prettyprint

2023年3月27日 星期一

[Raspberry Pi Pico W] BTstack: Ep 2. Bluetooth Classic SPP piconet -- remote control and text messaging

本文章介紹使用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:
在這篇文章中使用同一個function。
3. 建立SPP service 的SDP service record。
使定service record handle為0x10001。(SPP service class的UUID為0x1101)。

二、Pico W SPP client:
建立SPP client需要用到bluetooth stack 的portocol與profile如下圖所示:
如同SPP server一樣要先init l2cap, rfcomm和指定packet handler。
當HCI is working時執行gap_inquiry 收尋bluetooth device。

執行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;
}

2023年3月17日 星期五

[Raspberry Pi Pico (c-sdk)] Display: Ep 5 :TFT LCD 4-lines Serial(SPI) Driver -- A single Frame(128x160) takes only 11~12ms

本篇文章介紹使用RP2040 PIO(Raspberry Pi Pico開發板)功能,撰寫一個TFT LCD 4-line Serial Interface(SPI)驅動程式。有關8-bit parallel請參閱上篇文章[Raspberry Pi Pico (c-sdk)] Display: Ep 2 : PIO TFT LCD ILI9341 8-bit parallel C-code Driver

下圖示ST7735 datasheet中有關4-line interface的clock cycle。

SCL "H" and "L" pulse width MIN 為30ns。
另外跟據ILI9486 Datasheet內容為:
SCL "H" and "L" pulse width MIN 為15ns。

PIO程式碼設定約為22ns。如下圖所示:


以此設定輸出單一個128x160大小的frame 約需只要11~12ms,frame rate可達到80fps以上。

成果影片


程式碼:
pico_tft.pio
.program tft_pio_parallel
; CSX, D/CX, WRX, RDX --> gpio 15, 14, 13, 12  (set pins)
.wrap_target
start:
pull
set pins, 0b0011
mov x, osr             ;command code, if 0x0, command nop, only data
jmp !x param
set pins, 0b0001
out pins, 8      ;[1]
set pins, 0b0011 ;[1]
param:
set pins, 0b0111
pull
mov x, osr              ;how many parameters
jmp !x, start            ;no parameter return start
jmp x--, param_data         
param_data:
pull                    ; write data
set pins, 0b0101 
out pins, 8      ;[1] 
set pins, 0b0111 ;[1]
jmp x--, param_data
set pins, 0b1111
jmp start

.program tft_pio_serial
; CSX, D/CX(A0), SCL --> gpio 15, 14, 13  (side-set pins)
.side_set 3
.wrap_target
start:
pull                        side 0b100
mov x, osr                  side 0b000           ;command code, if 0x0, command nop, only data
jmp !x param                side 0b000
set y,7                     side 0b000
cmd_bit_loop:
out pins, 1                 side 0b000  [1];[2]
jmp y--, cmd_bit_loop       side 0b001  [1];[2]

param:
pull                        side 0b010
mov x, osr                  side 0b010            ;how many parameters
jmp !x, start               side 0b010           ;no parameter return start
jmp x--, param_data         side 0b010        
param_data:
pull                        side 0b010                ; write data
set y,7                     side 0b010
data_bit_loop:
out pins, 1                 side 0b010  [1];[2]     
jmp y--, data_bit_loop      side 0b011  [1];[2]
jmp x--, param_data         side 0b010
jmp start                   side 0b100
pico_tft.c
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "string.h"

#include "registers.h"
#include "pico_tft.pio.h"
#include "pico_tft.h"
#include "fonts/font_ubuntu_mono_24.h"
#include "hardware/dma.h"

#define MAX_BYTE_TRANS (TFT_WIDTH*TFT_HEIGHT*2)

#define PICO_TFT_SERIAL
//#define PICO_TFT_DMA

PIO tft_pio = pio1;
uint tft_sm=0;
uint in_out_base_pin=4;
uint set_base_pin=12;
uint sideset_base_pin=11;
uint s_in_out_base_pin=10;

int tft_dma_channel;

/*
\param frame_buff: frame buffer
\param src: source image
\param x: start x of source image
\param y: start y of source image
\param fx: start x of drawing in frame buffer
\param fy: start y of drawing in frame buffer
\param ow: source image width
param oh: source image height
*/
void fb_draw_image(uint8_t* frame_buffer, uint8_t *src, uint16_t x, uint16_t y, uint16_t fx, uint16_t fy, uint32_t ow, uint32_t oh, bool wrapx, bool wrapy, bool enable_transparent, uint16_t transparent_color) {
    uint16_t width = (ow > TFT_WIDTH)? TFT_WIDTH : ow;
    uint16_t height = (oh > TFT_HEIGHT) ? TFT_HEIGHT: oh;

    if (!wrapx && (fx+width > TFT_WIDTH)) width = TFT_WIDTH-fx; 
    if (!wrapy && (fy+height) > TFT_HEIGHT) height = TFT_HEIGHT-fy;
    uint32_t src_loc;
    
    for (int j=0; j < height; j++) {
        for (int i=0; i < width; i++) {
            src_loc=((x+i)%ow + ((y+j)%oh)*ow)*2;
            if (!enable_transparent || src[src_loc]!=transparent_color>>8 || src[src_loc+1] != transparent_color&0xff) { 
                frame_buffer[((i+fx)+(j+fy)*TFT_WIDTH)*2] = src[src_loc];
                frame_buffer[((i+fx)+(j+fy)*TFT_WIDTH)*2+1] = src[src_loc+1];
            }
        }
    }
}

/*
\param src: source image
\param dest: dest image buffer
\param x: source image x, coordinates
\param y: source image y coordinates
\param ow: source image width
param oh: source image height
\param width: dest image width
\param height: dest image height
*/
void tft_fill_partial_image(uint8_t *src, uint8_t *dest,uint16_t x, uint16_t y, uint32_t ow, uint32_t oh,uint16_t width, uint16_t height) {
    uint32_t src_loc;
    for (int j=0; j < height; j++) {
        for (int i=0; i < width; i++) {
            src_loc=((x+i)%ow + ((y+j)%oh)*ow)*2;
            dest[(i+j*width)*2] = src[src_loc];
            dest[(i+j*width)*2+1] = src[src_loc+1];
        }
    }
}

/*
\param src: source image
\param x: start x position
\param y: start y position
\param ow: source image width
\param oy: source image height
\param wrapx: warp x 
\param warpy: warp y
*/
void tft_draw_frame(uint8_t *src, uint16_t x, uint16_t y, uint16_t tx, uint16_t ty, uint32_t ow, uint32_t oh) {
    uint16_t width = (tx+ow > TFT_WIDTH)? TFT_WIDTH-tx : ow;
    uint16_t height = (ty+oh > TFT_HEIGHT) ? TFT_HEIGHT-ty: oh;

    uint32_t src_loc;
    uint32_t count = width*height*2;

    tft_set_address_window (tx, ty, tx+width-1, ty+height-1);
    pio_sm_restart(tft_pio, tft_sm);
    #ifdef PICO_TFT_SERIAL
    pio_sm_put_blocking(tft_pio, tft_sm, TFT_MEMORYWRITE << 24);
    #endif
    #ifdef PICO_TFT_PARALLEL
    pio_sm_put_blocking(tft_pio, tft_sm, TFT_MEMORYWRITE);
    #endif
    
    pio_sm_put_blocking(tft_pio, tft_sm, count);
    
    for (int j=0; j < height; j++) {
        for (int i=0; i < width; i++) {
            src_loc=((x+i)%ow + ((y+j)%oh)*ow)*2;
            #ifdef PICO_TFT_SERIAL
            pio_sm_put_blocking(tft_pio, tft_sm, src[src_loc]<<24);
            pio_sm_put_blocking(tft_pio, tft_sm, src[src_loc+1]<<24);
            #endif
            #ifdef PICO_TFT_PARALLEL
            pio_sm_put_blocking(tft_pio, tft_sm, src[src_loc]);
            pio_sm_put_blocking(tft_pio, tft_sm, src[src_loc+1]);
            #endif
        }
    }
}

void tft_draw_image_with_trasnparent_color(uint16_t x, uint16_t y, const tImage *bitmap, uint32_t transparent_color) {
    uint16_t width, height;
    if (x+bitmap->width > TFT_WIDTH) width = TFT_WIDTH-x; else width = bitmap->width;
    if (y+bitmap->height > TFT_HEIGHT) height = TFT_HEIGHT-y; else height = bitmap->height;

    uint16_t *color=(uint16_t*)bitmap->data;
    for (int j=0; j < height;j++) {
        for (int i=0; i < width;i++) {
            if (color[i+j*bitmap->width] != transparent_color) {
                
                tft_draw_pixel(x+i,y+j,color[i+j*(bitmap->width)]);
            }
        }
    }
    
}

void tft_cmd(uint32_t cmd, uint32_t count, uint8_t *param)
{
    pio_sm_restart(tft_pio, tft_sm);
    #ifdef PICO_TFT_SERIAL
    pio_sm_put_blocking(tft_pio, tft_sm, cmd << 24);
    #endif
    #ifdef PICO_TFT_PARALLEL
    pio_sm_put_blocking(tft_pio, tft_sm, cmd);
    #endif
    
    pio_sm_put_blocking(tft_pio, tft_sm, count);
    for (int i = 0; i < count; i++)
    {
        #ifdef PICO_TFT_SERIAL
        pio_sm_put_blocking(tft_pio, tft_sm, param[i]<<24);
        #endif
        #ifdef PICO_TFT_PARALLEL
        pio_sm_put_blocking(tft_pio, tft_sm, param[i]);
        #endif
    }
}
#ifdef PICO_TFT_DMA
void tft_cmd_dma(uint32_t cmd, uint32_t count, uint8_t *param)
{
    #ifdef PICO_TFT_SERIAL
    tft_cmd(cmd, count, param);
    return;
    #endif 
    pio_sm_restart(tft_pio, tft_sm);
    pio_sm_put_blocking(tft_pio, tft_sm, cmd);
    pio_sm_put_blocking(tft_pio, tft_sm, count);
    dma_channel_set_trans_count(tft_dma_channel, count >> DMA_SIZE_8, false);
    dma_channel_set_read_addr(tft_dma_channel, param, false);
    dma_channel_start(tft_dma_channel);
    dma_channel_wait_for_finish_blocking(tft_dma_channel);
    
}
#endif
void tft_pio_cmd_init(PIO pio, uint sm, uint in_out_base,  uint set_sideset, uint32_t freq) {
    uint offset=0;
    pio_sm_config c;
    #ifdef PICO_TFT_PARALLEL
    offset = pio_add_program(pio, &tft_pio_parallel_program);
    c = tft_pio_parallel_program_get_default_config(offset);
    for (int i=0; i < 8; i++) pio_gpio_init(pio, in_out_base+i);
    for (int i=0; i < 4; i++) pio_gpio_init(pio, set_sideset+i);
    pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);
    sm_config_set_in_pins(&c, in_out_base);
    sm_config_set_out_pins(&c, in_out_base, 8);
    sm_config_set_set_pins(&c, set_sideset, 4);
    sm_config_set_out_shift(&c, true, false, 8);
    sm_config_set_in_shift(&c, false, false, 8);
    #endif
    #ifdef PICO_TFT_SERIAL
    offset = pio_add_program(pio, &tft_pio_serial_program);
    c = tft_pio_serial_program_get_default_config(offset);
    pio_gpio_init(pio, in_out_base);
    for (int i=0; i < 3; i++) pio_gpio_init(pio, set_sideset+i);
    pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 1, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_sideset, 3, true);
    sm_config_set_in_pins(&c, in_out_base);
    sm_config_set_out_pins(&c, in_out_base, 1);
    sm_config_set_sideset_pins(&c, set_sideset);
    sm_config_set_out_shift(&c, false, false, 8);
    sm_config_set_in_shift(&c, true, false, 8);
    #endif
       
    //sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
    
    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    #ifdef PICO_TFT_DMA
    /*   DMA  */
    tft_dma_channel = dma_claim_unused_channel(true);
    dma_channel_config dc = dma_channel_get_default_config(tft_dma_channel);
    channel_config_set_write_increment(&dc, false);
    channel_config_set_read_increment(&dc, true);
    channel_config_set_dreq(&dc, pio_get_dreq(pio, sm, true));
    channel_config_set_transfer_data_size(&dc, DMA_SIZE_8); //DMA_SIZE_8,16,32
    
    dma_channel_configure(tft_dma_channel, &dc, (void*) (PIO1_BASE+PIO_TXF0_OFFSET), 
             NULL, MAX_BYTE_TRANS>> DMA_SIZE_8, false); //DMA_SIZE_8 or 16 or 32
    /*  DMA */
    #endif 
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

/* ili3941 draw functions*/
uint16_t tft_color_565RGB(uint8_t R, uint8_t G, uint8_t B) {
    uint16_t c;
    c = (((uint16_t)R)>>3)<<11 | (((uint16_t)G)>>2) << 5 | ((uint16_t)B)>>3;
    return c;
}
void tft_memory_write_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
	uint8_t addr[4];
    addr[0]=(uint8_t)(x1 >> 8);
    addr[1]= (uint8_t)(x1 & 0xff);
    addr[2]= (uint8_t)(x2 >> 8);
    addr[3]= (uint8_t)(x2 & 0xff);
    tft_cmd(TFT_COLADDRSET, 4,   addr);

    addr[0]=(uint8_t)(y1 >> 8);
    addr[1]= (uint8_t)(y1 & 0xff);
    addr[2]= (uint8_t)(y2 >> 8);
    addr[3]= (uint8_t)(y2 & 0xff);
	tft_cmd(TFT_PAGEADDRSET, 4,   addr );

    tft_cmd(TFT_MEMORYWRITE, 0, NULL);
}

void tft_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
	uint8_t addr[4];
    addr[0]=(uint8_t)(x1 >> 8);
    addr[1]= (uint8_t)(x1 & 0xff);
    addr[2]= (uint8_t)(x2 >> 8);
    addr[3]= (uint8_t)(x2 & 0xff);
    tft_cmd(TFT_COLADDRSET, 4,  addr);

    addr[0]=(uint8_t)(y1 >> 8);
    addr[1]= (uint8_t)(y1 & 0xff);
    addr[2]= (uint8_t)(y2 >> 8);
    addr[3]= (uint8_t)(y2 & 0xff);
	tft_cmd(TFT_PAGEADDRSET, 4,  addr );
}

/* put color at point*/
void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
  if ( x < 0 || x > TFT_WIDTH-1 || y < 0 || y > TFT_HEIGHT-1) return;
	tft_set_address_window(x,y,x,y);
    tft_cmd(TFT_MEMORYWRITE, 2,  (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)color});
}

void tft_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color) {
  if (x < 0) x=0;
  if (y < 0) y=0;
  if (x+width > TFT_WIDTH) width = TFT_WIDTH-x;
  if (y+height > TFT_HEIGHT) height = TFT_HEIGHT-y;
  tft_memory_write_window(x,y, x+width-1, y+height-1);
  for (int j = y; j < y+height; j++) 
    for (int i = x; i< x+width;i++)
      tft_cmd(TFT_NOP, 2,  (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)(color&0xff)});
}


void tft_draw_bitmap(uint16_t x, uint16_t y, const tImage *bitmap)
{
 
	uint32_t width = 0, height = 0;
	width = bitmap->width;
	height = bitmap->height;

	uint32_t total_pixels = width * height*2;

	tft_set_address_window (x, y, x + width-1, y + height-1);
    tft_cmd_dma(TFT_MEMORYWRITE, total_pixels,  (uint8_t*)(bitmap->data));
   
}
void tft_init_config() {
	tft_cmd(TFT_SOFTRESET, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYOFF, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_PIXELFORMAT, 1,  (uint8_t[1]){0x55}); //0x55
    tft_cmd(TFT_POWERCONTROL1, 1,  (uint8_t[1]){0x05}); // 0x05 :3.3V
    tft_cmd(TFT_POWERCONTROL2, 1,  (uint8_t[1]){0x10});
    tft_cmd(TFT_VCOMCONTROL1, 2,  (uint8_t[2]){0x3E, 0x28});
    tft_cmd(TFT_VCOMCONTROL2, 1,  (uint8_t[1]){0x86});
    tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){0x08}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    tft_cmd(TFT_FRAMECONTROL, 2,  (uint8_t[2]){0b00, 0x1B}); // Default 70Hz:0x1B
    tft_cmd(TFT_DISPLAYFUNC, 4,  (uint8_t[4]){0x0A, 0x82, 0x27, 0x04}); //0a,a2,27,04
    tft_cmd(TFT_GAMMASET, 1,  (uint8_t[1]){0x01});
  
  	//tft_cmd(tft_PGAMCOR, 15, 0xFF, (uint8_t[15]){ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00 });
    //tft_cmd(tft_NGAMCOR,  15,0xFF, (uint8_t[15]){ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f }); 
    
    tft_cmd(TFT_SLEEPOUT, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYON, 0,  NULL);
    sleep_ms(500);
    
}
void tft_init() {
    #ifdef PICO_TFT_PARALLEL
        tft_pio_cmd_init(tft_pio, tft_sm, in_out_base_pin, set_base_pin, 70000000);  //pio freq
    
    #endif
    #ifdef PICO_TFT_SERIAL
        tft_pio_cmd_init(tft_pio, tft_sm, s_in_out_base_pin, sideset_base_pin, 90000000);  //pio freq
	#endif 
    tft_init_config();
    
}

void tft_rotate_image(const uint16_t *src, uint16_t *dest, uint16_t width, uint16_t height, uint16_t deg) {
    
    double sinx = sin((deg)/180.0 * M_PI);  
    double cosx = cos((deg)/180.0 * M_PI);

    int xCenter = height/2;        // Rotate image by its center.
    int yCenter = width/2;
    int xt,yt,xRotate,yRotate;

    for(int x=0; x<height; x++) {
        xt = x - xCenter;
        double xt_cosx = xt*cosx;
        double xt_sinx = xt*sinx;
        for(int y=0; y<width; y++) {
            yt = y - yCenter;
            xRotate = (int)(lround(xt_cosx - (yt*sinx)) + xCenter);
            yRotate = lround((yt*cosx) + xt_sinx) + yCenter;  
            if( (xRotate >= 0) && (xRotate < height) && (yRotate >= 0) && (yRotate < width) ) {
                dest[x*width+y] = src[xRotate*width+yRotate];
            } else {
                dest[x*width+y]=0xffff;
            }
        }
    }

}
pico_tft.h
#ifndef  _TFT_H_
#define _TFT_H_

#define TFT_WIDTH   128
#define TFT_HEIGHT   160

#include "pico/stdlib.h"

#include "fonts/bitmap_typedefs.h"
#include "hardware/pio.h"
#include "math.h"


void tft_init();
void tft_init_config();
void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color);
void tft_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void tft_cmd(uint32_t cmd, uint32_t count,  uint8_t *param);
void tft_cmd_dma(uint32_t cmd, uint32_t count,  uint8_t *param);
uint16_t tft_color_565RGB(uint8_t R, uint8_t G, uint8_t B);
void tft_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color);
void tft_draw_bitmap(uint16_t x, uint16_t y, const tImage *bitmap);
void tft_pio_cmd_init(PIO pio, uint sm, uint out_base,  uint set_base, uint32_t freq);
void tft_rotate_image(const uint16_t *src, uint16_t *dest, uint16_t width, uint16_t height, uint16_t deg);
void tft_fill_partial_image(uint8_t *src, uint8_t *dest,uint16_t x, uint16_t y, uint32_t ow, uint32_t oh,uint16_t width, uint16_t height);
void tft_draw_frame(uint8_t *src, uint16_t x, uint16_t y, uint16_t tx, uint16_t ty, uint32_t ow, uint32_t oh);
void tft_draw_image_with_trasnparent_color(uint16_t x, uint16_t y, const tImage *bitmap, uint32_t transparent_color);
void fb_draw_image(uint8_t* frame_buffer, uint8_t *src, uint16_t x, uint16_t y, uint16_t fx, uint16_t fy, uint32_t ow, uint32_t oh, bool wrapx, bool wrapy, bool enable_transparent, uint16_t transparent_color);
#endif
測試主程式
 #include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"

#include "pico_tft.h"
#include "tft_string.h"
#include "fonts/font_ubuntu_mono_24.h"
#include "registers.h"
#include "time.h"
#include "stdlib.h"
#include "string.h"
#include "fonts/img1.h"
#include "fonts/img2.h"
#include "fonts/gear.h"
#include "pico/multicore.h"
#include "fonts/bg.h"
#include "fonts/w1.h"
#include "fonts/w2.h"
#include "fonts/w3.h"

typedef struct _rotate_obj_t {
    uint8_t *srcimg;
    uint8_t *destimg;
    uint16_t width;
    uint16_t height;
    uint16_t angle;
} rotate_obj_t;

void core1_rotate() {
    while(1) { 
        rotate_obj_t *rimg = (rotate_obj_t*)multicore_fifo_pop_blocking();
        tft_rotate_image((uint16_t*)rimg->srcimg,(uint16_t*)rimg->destimg, rimg->width, rimg->height, rimg->angle);
        multicore_fifo_push_blocking(1);
    }
    
}

int main()
{

    stdio_init_all();
    tft_init();
    
    multicore_launch_core1(core1_rotate);

    rotate_obj_t *rotate_img = (rotate_obj_t*) calloc(1, sizeof(rotate_obj_t));
    uint8_t *read_buff = (uint8_t*) calloc(TFT_WIDTH*TFT_HEIGHT*2, sizeof(uint8_t));  //frame buffer
    if (!read_buff) {
        printf("alloc memory error\n");
        return 0;
    }
     uint8_t *write_buff = (uint8_t*) calloc(TFT_WIDTH*TFT_HEIGHT*2, sizeof(uint8_t));  //frame buffer
    if (!write_buff) {
        printf("alloc memory error\n");
        return 0;
    }

    uint8_t *tmp_buff;
    absolute_time_t t1, t2;

/* start testing */
// single frame =============================
    tft_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0xffff);
    tft_draw_string(20,20,"Starting", 0x001f, &font_ubuntu_mono_24);
    printf("\n=== Start testing ===\n");
    sleep_ms(1000);
    printf("   single frame...\n");
    t1 = get_absolute_time();
    tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
    tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  (uint8_t*)img1.data);
    t2=get_absolute_time();
    printf("single frame: image1 store in XIP flash (%f fps)        --time:%"PRIu64"us\n",(1000000.0)/absolute_time_diff_us(t1, t2),absolute_time_diff_us(t1, t2));
    sleep_ms(1000);
    t1 = get_absolute_time();
    tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
    tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  (uint8_t*)img2.data);
    t2=get_absolute_time();
    printf("single frame: image2 store in XIP flash (%f fps)        --time:%"PRIu64"us\n",(1000000.0)/absolute_time_diff_us(t1, t2),absolute_time_diff_us(t1, t2));
    sleep_ms(1000);

    //   Animation shift background ========
    printf("\n   Animation shift background...\n");
    t1 = get_absolute_time();
    for (int i =0; i < bg.width; i+=2) {
        fb_draw_image(read_buff, (uint8_t*)bg.data, i,60, 0,0,bg.width, bg.height, false, false, false, 0x0000);
        tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
        tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  read_buff);
    }
    t2=get_absolute_time();
    printf("shift %u frames (%f fps)                                --time:%"PRIu64"us\n", bg.width/2, (float)bg.width/2*1000000/absolute_time_diff_us(t1, t2),absolute_time_diff_us(t1, t2));

    //   animation shift background and man walk ========
    printf("\n   Animation shift background and man walk...\n");
    //for (int j=0; j < bg.height-TFT_HEIGHT; j+=60){ 
        t1= get_absolute_time();
        for (int i =0; i < bg.width; i++) {
            fb_draw_image(read_buff, (uint8_t*)bg.data, i,0, 0,0,bg.width, bg.height, false, false, false, 0x0000);
            if (i%18< 6) fb_draw_image(read_buff, (uint8_t*)w1.data, 0,0,30,30, w1.width, w1.height, false, false, true, 0x0000);
            if (i%18>=6  && i%18 <12) fb_draw_image(read_buff, (uint8_t*)w2.data, 0,0,30,30, w2.width, w2.height, false, false, true, 0x0000);
            if (i%18>=12) fb_draw_image(read_buff, (uint8_t*)w3.data, 0,0,30,30, w3.width, w3.height, false, false, true, 0x0000);
            tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
            tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  read_buff);
        }
        t2=get_absolute_time();
        printf("shift %u frames (%f fps)                         --time:%"PRIu64"us\n", bg.width, (float)bg.width*1000000/absolute_time_diff_us(t1, t2),absolute_time_diff_us(t1, t2));

        t1= get_absolute_time();
        for (int i =0; i < bg.width; i++) {
            fb_draw_image(read_buff, (uint8_t*)bg.data, i,i, 0,0,bg.width, bg.height, true, true, false, 0x0000);
            if (i%18< 6) fb_draw_image(read_buff, (uint8_t*)w1.data, 0,0,30,30, w1.width, w1.height, false, false, true, 0x0000);
            if (i%18>=6  && i%18 <12) fb_draw_image(read_buff, (uint8_t*)w2.data, 0,0,30,30, w2.width, w2.height, false, false, true, 0x0000);
            if (i%18>=12) fb_draw_image(read_buff, (uint8_t*)w3.data, 0,0,30,30, w3.width, w3.height, false, false, true, 0x0000);
            tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
            tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  read_buff);
        }
        t2=get_absolute_time();
        printf("shift %u frames (%f fps)                         --time:%"PRIu64"us\n", bg.width, (float)bg.width*1000000/absolute_time_diff_us(t1, t2),absolute_time_diff_us(t1, t2));
    
    //}
    //  random 50 rectangles in a frame================ 
    printf("\n   Random 50 rectangles in a frame\n");
    time_t t;
    srand((unsigned) time(&t));
    char src_buf[100*100*2];
    uint8_t *spt;
    int x,y,width,color;
    int boxs[100][4]; //(x,y,width, color-h, cololr-l)
    //==== 100 random rectangles
    for (int i=0; i < 100; i++) {
        y=rand()%TFT_HEIGHT;
        x=rand()%TFT_WIDTH;
        width=rand()%40;
        if (y+width >=TFT_HEIGHT) y=TFT_HEIGHT-y;
        if (x+width >=TFT_WIDTH) x=TFT_WIDTH-x;
        boxs[i][0]=x;
        boxs[i][1]=y;
        boxs[i][2]=width;
        boxs[i][3]=rand()%0xff;
    } 
    int p;
    
    int i, j, b; // repeat test times

    for (int rep=0; rep < 2; rep++) { 
    t1= get_absolute_time();
    for (i=0; i < 100; i++) {  
        memset(write_buff, 0, TFT_WIDTH*TFT_HEIGHT*2);
        for (j=0; j < 50; j++) { 
            p = rand()%100;
            spt= (write_buff+(boxs[p][0]+boxs[p][1]*TFT_WIDTH)*2);
            for (int k =0; k < boxs[p][2]; k++){
                memset((spt+k*TFT_WIDTH*2), boxs[p][3], boxs[p][2]*2); 
            }
        }
        b=j;
        tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
        tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  write_buff);
    }
    t2=get_absolute_time();
    printf("%d frames(%d Boxs) in RAM)                              --time:%"PRIu64"us\n",i, j, absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps, pause 1 second to start next test\n\n", (float)i/(absolute_time_diff_us(t1, t2))*1000000);
    sleep_ms(1000);
    }
    //   coror frame test
    printf("\n   Color frame test...\n");
    t1 = get_absolute_time();
    tft_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0xf800);
    tft_draw_string(20,20,"Red", 0xffff,&font_ubuntu_mono_24);
    t2=get_absolute_time();
    printf("1 color frame(with text)                                  --time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 1000000.0/(absolute_time_diff_us(t1, t2)));
    sleep_ms(1000);

    t1 = get_absolute_time();
    tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
    tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  (uint8_t*)image_data_img1);
    t2=get_absolute_time();
    printf("1 image frame                                           --time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 1000000.0/(absolute_time_diff_us(t1, t2)));
    sleep_ms(1000);

    t1 = get_absolute_time();
    tft_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0x7E0);
    tft_draw_string(20,20,"Green", 0xf800, &font_ubuntu_mono_24);
    t2=get_absolute_time();
    printf("1 color frame(with text)                                  --time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    
    printf("Frame Rate: %ffps\n\n", 1000000.0/(absolute_time_diff_us(t1, t2)));sleep_ms(1000);

    t1 = get_absolute_time();
    tft_set_address_window (0, 0, TFT_WIDTH-1, TFT_HEIGHT-1);
    tft_cmd(TFT_MEMORYWRITE, TFT_WIDTH*TFT_HEIGHT*2,  (uint8_t*)image_data_img2);
    t2=get_absolute_time();
    printf("1 image frame                                           --time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 1000000.0/(absolute_time_diff_us(t1, t2)));
    sleep_ms(1000);

    t1 = get_absolute_time();
    tft_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0x001f);
    tft_draw_string(20,20,"Blue", 0xffff, &font_ubuntu_mono_24);
    t2=get_absolute_time();
    printf("1 color frame(with text)                                  --time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 1000000.0/(absolute_time_diff_us(t1, t2)));
    sleep_ms(1000);

    tft_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0xffff);

   // image rotation test
   printf("\n   Image rotation test...\n");
    t1 = get_absolute_time();
    for (int i =0; i < 360; i+=2) {
        tft_rotate_image((uint16_t*)image_data_gear, (uint16_t*)write_buff, 100, 100, i);
        tft_set_address_window (13, 29, 112, 128);
        tft_cmd(TFT_MEMORYWRITE, 100*100*2,  (uint8_t*)write_buff);
    }
    t2=get_absolute_time();
    printf("rotation 360 degrees step 2 degree(100*100 image in flash)    --time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 180.0/(absolute_time_diff_us(t1, t2))*1000000);

    memcpy(src_buf, image_data_gear, 100*100*2);
    t1 = get_absolute_time();
    for (int i =360; i>0; i-=2) {
        tft_rotate_image((uint16_t*)src_buf, (uint16_t*)write_buff, 100, 100, i);
        tft_set_address_window (13, 29, 112, 128);
        tft_cmd(TFT_MEMORYWRITE, 100*100*2,  (uint8_t*)write_buff);
    }
    t2=get_absolute_time();
    printf("rotation 360 degree step 2 degree(100*100 source image in RAM)--time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 180.0/(absolute_time_diff_us(t1, t2))*1000000);

     /* core1 rotate*/
     rotate_img->srcimg = (uint8_t*)image_data_gear;
    rotate_img->width=100;
    rotate_img->height=100;
    memcpy(write_buff, image_data_gear, 100*100*2);
    
    t1 = get_absolute_time();
    for (int i =0; i < 360; i+=2) {
        rotate_img->angle=i;
        rotate_img->destimg = read_buff;
        multicore_fifo_push_blocking((uint32_t)rotate_img);

        tft_set_address_window (13, 29, 112, 128);
        tft_cmd(TFT_MEMORYWRITE, 100*100*2,  (uint8_t*)write_buff);

        uint32_t r = multicore_fifo_pop_blocking();


        tmp_buff = read_buff;
        read_buff= write_buff;
        write_buff=tmp_buff;
    }
    t2=get_absolute_time();
    printf("rotation 360 degree step 2 degree(100*100) multicore          --time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 180.0/(absolute_time_diff_us(t1, t2))*1000000);

    memcpy(src_buf, image_data_gear, 100*100*2);
    tft_rotate_image((uint16_t*)src_buf, (uint16_t*)write_buff, 100, 100, 10);
    t1 = get_absolute_time();
    for (int i =0; i < 360; i++) {
        tft_set_address_window (13, 29, 112, 128);
        tft_cmd(TFT_MEMORYWRITE, 100*100*2,  (uint8_t*)write_buff);
        //tft_set_address_window (13, 29, 112, 128);
        tft_cmd(TFT_MEMORYWRITE, 100*100*2,  (uint8_t*)src_buf);
    }
    t2=get_absolute_time();
    float factor = (100.0*100.0)/(128*160);
    printf("rotation forward and backward 10 degree(double buffers in RAM)--time:%"PRIu64"us\n",absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n\n", 360.0*2*factor/(absolute_time_diff_us(t1, t2))*1000000);
   
    printf("The End\n");
    tft_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0xffff);
    tft_draw_string(20,20,"The End", 0x001f, &font_ubuntu_mono_24);
    

    while(1) {
        tight_loop_contents();
    }
    puts("Hello, world!");

    return 0;
}

2023年3月10日 星期五

[Raspberry Pi Pico (c-sdk)] BTStack: Ep 1. A2DP, AVRCP, GAP and Bluetooth Speaker

 Pi Pico SDK 1.5.0後開始支援CYW4334 bluetooth 驅動程式,本篇文章使用以實做Raspberry Pi Pico W的藍芽喇叭(A2DP Sink)。

使用元件:

  1. Raspberry Pi Pico W 
  2. MAX98357A DAC
  3. 3W喇叭
  4. Ky-040 Rotary encoder


軟體整合使用BTstack a2dp_sink_demo.c範例程式與前篇文章PIO I2S程式Raspberry Pi Pico PIO(Programmable IO) Episode 4: I2S audio (Play WAVE File In SDCard from MAX98357A DAC)來實做一個藍芽喇叭。

a2dp_sink_demo.c已經把GAP, A2DP, AVRCP軟體部份已經完成。

本文章介紹如何整合到前篇PIO I2S播放器。資料流程如下圖:


如圖所示,同時使用到Raspberry Pi Pioc W的五大元件同時運作:Core0、Core1、DMA Controller、PIO0與PIO1。
Core0:負責與A2DP Source溝通,把接收到SBC Package送到SBC ring buffer。
Core1:將SBC ring buffer取出SBC資料解碼成PCM後送到PCM ring buffer。
DMA Controller:從Ring buffer取出 PCM送給PIO1。
PIO1: 輸出PCM到I2S DAC由喇叭播出音效。
PIO0:接Rotary Encoder,解譯順時鐘、逆時鐘或按下按鈕。由AVRCP送出Forward, Backward與play/pause指令。

軟體說明:
以不修改a2dp_sink_demo.c程式碼前提下整合。因為a2dp_sink_demo.c使用static變數。因此使用#include到主程式中。

使用在a2dp_sink_demo.c程式中a2dp_sink_demo_a2dp_connection與a2dp_sink_demo_avrcp_connection兩個變數,以便在主程式中檢查連線狀態與送出avrcp指令。


主程式要實做
btastack_audio.h中的struct "btstack_audio_sink_t;",即:
   int (*init)(uint8_t channels,
                uint32_t samplerate, 
                void (*playback) (int16_t * buffer, uint16_t num_samples));    
    uint32_t (*get_samplerate)(void);   
    void (*set_volume)(uint8_t volume);
    void (*start_stream)(void);
    void (*stop_stream)(void);
    void (*close)(void);
五個callback function與
const btstack_audio_sink_t * btstack_audio_sink_get_instance(void);
void btstack_audio_sink_set_instance(const btstack_audio_sink_t * audio_sink_impl);
兩個functions。
Rotary Encoder的PIO程式製作請參閱前篇說明:

請參閱『成果影片』解說。其他詳細程式碼附於文末。

成果影片:



程式碼:

  • picow_bt_speaker.c

#include "hardware/clocks.h"
#include "pico/cyw43_arch.h"
#include "pico/btstack_cyw43.h"
#include "i2s_pio_dma.h"
#include "pico/multicore.h"
#include "pico/util/queue.h"
#include "pio_rotary_encoder.h"

#include "btstack_audio.h"
#include "a2dp_sink_demo.c"


#define CLK_PIN  13
#define DT_PIN   14
#define SW_PIN   15
#define I2S_AUDIO_PIO   pio1
#define I2S_AUDIO_SM    1
#define I2S_AUIDO_PIO_BASE 20   /* 20:BCK, 21:LRCK*/
#define I2S_AUDIO_PIO_DIN  22

#define WAVE_DECODE_FRAME 128
#define PCM_STORAGE_SIZE 1024*60    /* 60K for ring buffer */
#define DMA_READ_MULT   4 
static uint8_t pcm_audio_storage[PCM_STORAGE_SIZE];
static btstack_ring_buffer_t pcm_audio_ring_buffer;

i2s_audio_param_t i2s_audio;  // audio_sink_init, start_stream, stop, close

a2dp_sink_demo_avrcp_connection_t *bt_avrcp_connection;
a2dp_sink_demo_a2dp_connection_t *bt_a2dp_connection;

static void (*playback_callback)(int16_t * buffer, uint16_t num_samples);

uint8_t dma_buff1[WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT];
uint8_t dma_buff2[WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT];
uint8_t *dma_read_buff_pt=dma_buff1;
uint8_t *dma_write_buff_pt=dma_buff2;
uint8_t *tmp_pt;

uint32_t ring_buffer_empty_count=0;
uint32_t ring_buffer_full_count=0;
uint32_t ring_bytes_read=0;
uint32_t ring_bytes_write=0;
uint32_t ring_bytes;

bool bt_audio_playing=false;

void i2s_audio_dma_cb() {
    if (dma_channel_get_irq1_status(i2s_audio.dma_chan_num) ) {
        dma_channel_acknowledge_irq1(i2s_audio.dma_chan_num);
        if (bt_audio_playing){ 
            tmp_pt = dma_read_buff_pt;
            dma_read_buff_pt = dma_write_buff_pt;
            dma_write_buff_pt = tmp_pt;

            ring_bytes = ring_bytes_read;
            ring_bytes_read = ring_bytes_write;
            ring_bytes_write = ring_bytes;

            dma_channel_set_trans_count(i2s_audio.dma_chan_num, ring_bytes_write>>DMA_SIZE_16, false);
            dma_channel_set_read_addr(i2s_audio.dma_chan_num, (uint8_t*)dma_write_buff_pt,true);
            
            if (btstack_ring_buffer_empty(&pcm_audio_ring_buffer)) ring_buffer_empty_count++;
            
            btstack_ring_buffer_read(&pcm_audio_ring_buffer, dma_read_buff_pt,WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT, &ring_bytes_read);          
        }
    }
 }

void core1_decode() {
    int write_ok=0;
    uint8_t buff[WAVE_DECODE_FRAME*BYTES_PER_FRAME];
    while(1) {
        multicore_fifo_pop_blocking();
        while (bt_audio_playing) {
         
            if (write_ok == 0) { 
                playback_callback((uint16_t*)buff, WAVE_DECODE_FRAME);
            } else {
                ring_buffer_full_count++;
            }
            write_ok=btstack_ring_buffer_write(&pcm_audio_ring_buffer, buff, WAVE_DECODE_FRAME*BYTES_PER_FRAME);
        } 
    }
}
/* audio_sink_init :
    get bitsPerSample, numberOfChannel and SampleRate*/
int bt_audio_sink_init(uint8_t channels, uint32_t samplerate, void (*playback)(int16_t *buffer, uint16_t num_samples) ){
	btstack_assert(playback != NULL);
    btstack_assert(channels != 0);
    btstack_ring_buffer_init(&pcm_audio_ring_buffer, pcm_audio_storage, sizeof(pcm_audio_storage));
	playback_callback  = playback;
    bt_audio_playing=false;

    /* check a2dp connection status */
    bt_a2dp_connection = &a2dp_sink_demo_a2dp_connection;
    /*  avrcp : forward, backward, play/pause */
    bt_avrcp_connection = &a2dp_sink_demo_avrcp_connection;
	
    //i2s_audio.bitsPerSample = bt_a2dp_connection->sbc_configuration.block_length;
    i2s_audio.bitsPerSample = 16;
	i2s_audio.numberOfChannel = channels;
	i2s_audio.sampleRate = samplerate;

    memset(dma_read_buff_pt, 0, WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT);
    memset(dma_write_buff_pt, 0, WAVE_DECODE_FRAME*BYTES_PER_FRAME*DMA_READ_MULT);
    dma_channel_set_trans_count(i2s_audio.dma_chan_num, 0, false);
    dma_channel_set_read_addr(i2s_audio.dma_chan_num, (uint8_t*)dma_read_buff_pt,false);

	i2s_audio.i2s_dma_cb=i2s_audio_dma_cb;
    i2s_audio_init(I2S_AUDIO_PIO, I2S_AUDIO_SM, I2S_AUIDO_PIO_BASE, I2S_AUDIO_PIO_DIN, &i2s_audio);
    i2s_audio_enable(I2S_AUDIO_PIO, I2S_AUDIO_SM);
 
    multicore_launch_core1(core1_decode);
       
    return 1;
}

void bt_audio_sink_set_volume(uint8_t volume) {
    UNUSED(volume);
}

void bt_audio_sink_start_stream() {
    bt_audio_playing = true;
    multicore_fifo_push_blocking(1);
    dma_channel_start(i2s_audio.dma_chan_num);    
}

void bt_audio_sink_stop_stream() {
    bt_audio_playing=false;
	dma_channel_wait_for_finish_blocking(i2s_audio.dma_chan_num);
    pio_sm_clear_fifos(I2S_AUDIO_PIO, I2S_AUDIO_SM);
    i2s_audio_enable(I2S_AUDIO_PIO, I2S_AUDIO_SM);
    
    btstack_ring_buffer_reset(&pcm_audio_ring_buffer);
    
    printf("==== ring buffer full:%d, ring buffer empty:%d\n", ring_buffer_full_count, ring_buffer_empty_count);
    ring_buffer_full_count=0;
    ring_buffer_empty_count=0;
}

void bt_audio_sink_close() {
    
	i2s_audio_reset(I2S_AUDIO_PIO, I2S_AUDIO_SM);
    multicore_reset_core1();
}
static const btstack_audio_sink_t  btstack_audio_sink_instance= {
	.init = &bt_audio_sink_init,
    .set_volume = &bt_audio_sink_set_volume,
    .start_stream = &bt_audio_sink_start_stream,
    .stop_stream = &bt_audio_sink_stop_stream,
    .close = &bt_audio_sink_close,
};



/**
 * @brief Get BTstack Audio Sink Instance
 * @return btstack_audio_sink implementation
 */
const btstack_audio_sink_t * btstack_audio_sink_get_instance(void){
	return &btstack_audio_sink_instance;
}
uint8_t volume = 50;
void rotary_encoder_cb(queue_t* q ) {
    PIO_RE_QUEUE_T data;

    if (!queue_is_empty(q)) { 
        queue_remove_blocking(q, &data);
        printf("action:%d\n",data.action);
   
        if (data.action == PIO_RE_CLOCKWISE) 
        {
            if (bt_avrcp_connection->playing == 0 || bt_avrcp_connection->playing == 1) {
                avrcp_controller_forward(bt_avrcp_connection->avrcp_cid);
            }
        }

        if (data.action == PIO_RE_COUNTERCLOCKWISE) 
        {
            if (bt_avrcp_connection->playing == 0 || bt_avrcp_connection->playing == 1) {
                avrcp_controller_backward(bt_avrcp_connection->avrcp_cid);
            }
        }

        if (data.action == PIO_RE_SWPRESS) 
        {
            if (bt_avrcp_connection->playing == 0) {
                avrcp_controller_play(bt_avrcp_connection->avrcp_cid);
            }
            if (bt_avrcp_connection->playing == 1) {
                avrcp_controller_pause(bt_avrcp_connection->avrcp_cid);
            }
        }
    }
}

int main()
{
    stdio_init_all();
    if (cyw43_arch_init()) {
        puts("cyw43_arch_init error");
        return 0;
    }

    cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN,false);
    btstack_main(1, NULL);  // call a2dp_sink_demo.c
    gap_set_local_name("PicoW BT");
  
    pio_rotary_encoder_default_init();
    pio_rotary_encoder_cb(rotary_encoder_cb);
    
    while(1) {

        switch(bt_a2dp_connection->stream_state) {
            case STREAM_STATE_CLOSED:
            cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
            break;
            case STREAM_STATE_OPEN:
            cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
            break;
            case STREAM_STATE_PAUSED:
            cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
            break;
            case STREAM_STATE_PLAYING:
            cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN,!cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN));
            break;
        }
       
        sleep_ms(50);
     
    }
    return 0;
}