prettyprint

2023年11月22日 星期三

Raspberry Pi || Node-RED || Pico W || TLS: Ep2. MQTT (mosquitto) 2wayauth

 本文章介紹在Raspberry Pi 3B+上建立Mosquitto MQTT Broker。並在Node-RED 與Pico W上互相Publish 與subscribe message。mosquitto 設定需要user name與password。並啟用TLS 2 way authentication功能。

有關Mosquitto MQTT Broker詳細設定步驟請參閱「MQTT 簡介:使用Mosquitto Broker」文章介紹。

本專案詳細設定步驟請參閱下列影片。

成果影片:





程式碼:

主程式:

  • picow_tls_mqtt.c


#include "stdio.h"
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "picow_tls_mqtt.h"
#include "picow_tls_cert.h"
#include "lwip/apps/mqtt.h"
#include "lwip/altcp.h"

#include "pio_rotary_encoder.h"
#include "ws2812.h"

#define WS2812_TOTAL_PIXELS 12
#define WIFI_SSID           "your-ssid"
#define WIFI_PASSWORD       "your-password"
#define SERVER_IP           "your-mqtt-broker-ip"

static mqtt_client_instance_t mqtt_client;
static int8_t rotary_value=0;
static uint32_t ws2812_color=0;
uint8_t pub_buff[1024];

void update_ws2812_color() {
    for (int i=0; i < rotary_value; i++) {
      ws2812_put_pixel_rgb(ws2812_color);
    }
    for (int i=rotary_value; i < WS2812_TOTAL_PIXELS; i++) {
      ws2812_put_pixel_rgb(0x0);
    }
}

void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags)
{
  mqtt_client_instance_t* client_inst = (mqtt_client_instance_t*)arg;
  LWIP_UNUSED_ARG(data);
  memcpy(client_inst->sub_message+client_inst->sub_message_offset, data, len);
  client_inst->sub_message_offset += len;
  if (flags == MQTT_DATA_FLAG_LAST) {
      client_inst->sub_message_recved = true;
      client_inst->sub_message[client_inst->sub_message_offset]=0;
      ws2812_color = strtol(client_inst->sub_message, NULL, 16);
      update_ws2812_color();
  }

}

void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len)
{
    mqtt_client_instance_t* client_inst = (mqtt_client_instance_t*)arg;
    client_inst->sub_message_offset=0;
    client_inst->sub_message_recved=false;
    strcpy(client_inst->sub_topic, topic);
}

void mqtt_request_cb(void *arg, err_t err)
{
  mqtt_client_instance_t* client_inst = (mqtt_client_instance_t*)arg;
  client_inst->connected=true;
  //LWIP_PLATFORM_DIAG(("MQTT client \"%s\" request cb: err %d\n", client_inst->mqtt_connect_client_info.client_id, (int)err));
}

void mqtt_pub_request_cb(void *arg, err_t err)
{
  mqtt_client_instance_t* client_inst = (mqtt_client_instance_t*)arg;
  //LWIP_PLATFORM_DIAG(("MQTT client \"%s\" pub request cb: err %d\n", client_inst->mqtt_connect_client_info.client_id, (int)err));
}

void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
{
    mqtt_client_instance_t* client_inst = (mqtt_client_instance_t*)arg;
    if (status == MQTT_CONNECT_ACCEPTED) {
      mqtt_sub_unsub(client,
            "ws2812_color", 1,
            mqtt_request_cb, LWIP_CONST_CAST(void*, client_inst),
            1);
      
    }
}

void rotary_encoder_cb(uint8_t action) {
  if (action == PIO_RE_CLOCKWISE || action == PIO_RE_COUNTERCLOCKWISE) {
    if (action == PIO_RE_CLOCKWISE) {
      rotary_value -= 1;
      if (rotary_value < 0) rotary_value=0;
    }
    if (action == PIO_RE_COUNTERCLOCKWISE) {
      rotary_value += 1;
      if (rotary_value > 12) rotary_value=12;
    }
  }
}

int main()
{
    int wifi_stat;
    err_t mqtt_stat;
    stdio_init_all();
    if (cyw43_arch_init()) {
        printf("cyw43 init error\n");
        return 0;
    }
    pio_rotary_encoder_default_init();
    pio_rotary_encoder_set_callback(rotary_encoder_cb);
    ws2812_default_pio_init();

    for (int i=0; i < WS2812_TOTAL_PIXELS; i++) ws2812_put_pixel(0x0);
    sleep_ms(50);
    //led: red
    ws2812_put_pixel_rgb(0xff0000);

    // connect to WiFi
    cyw43_arch_enable_sta_mode();
    wifi_stat = cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000);
    if (wifi_stat) {
        printf("wifi connect error:%d\n", wifi_stat);
        return 0;
    }
    printf("Wifi connected\n");
    //led: blue
    ws2812_put_pixel_rgb(0xff);

    //mqtt_client.tls_config = altcp_tls_create_config_client(ca_cert, sizeof(ca_cert));  
    mqtt_client.tls_config = altcp_tls_create_config_client_2wayauth(ca_cert, sizeof(ca_cert), mqtt_client_key, sizeof(mqtt_client_key), NULL, 0, mqtt_client_cert, sizeof(mqtt_client_cert));
    assert(mqtt_client.tls_config);
 
    /*
    // if DNS query
    mbedtls_ssl_set_hostname(altcp_tls_context(tls_client->tpcb), SERVER_NAME);
    tls_client->dns_found=false;
    cyw43_arch_lwip_begin();
    
    tcp_stat = dns_gethostbyname(SERVER_NAME, &tls_client->server_addr, tls_client_dns_found, tls_client);     
    if (tcp_stat == ERR_OK) printf("dns err ok\n");
    if (tcp_stat == ERR_INPROGRESS) printf("dns inprogress...\n");
    while(!tls_client->dns_found && tcp_stat != ERR_OK) {
         cyw43_arch_poll();
        sleep_ms(10);
    }

    */
    // if not by dns
    ipaddr_aton(SERVER_IP, &mqtt_client.ipaddr);
   
    // connect to mqtt server;
    mqtt_client.mqtt_connect_client_info = (struct mqtt_connect_client_info_t){
      "clientid", //client_id;
      "username", //client_user;
      "password", //client_pass;
      100,//keep_alive;
      NULL, // will_topic;
      NULL, // will_msg;
      0, //will_qos;
      0,// will_retain;
#if LWIP_ALTCP && LWIP_ALTCP_TLS
      mqtt_client.tls_config //tls_config;
#endif
    };

    mqtt_client.client = mqtt_client_new();
    //mbedtls_ssl_set_hostname(altcp_tls_context(conn)), SERVER_NAME);

    mqtt_set_inpub_callback(mqtt_client.client,
          mqtt_incoming_publish_cb,
          mqtt_incoming_data_cb,
          LWIP_CONST_CAST(void*, &mqtt_client.mqtt_connect_client_info));
    mqtt_client.connected=false;
    cyw43_arch_lwip_begin();
    mqtt_stat = mqtt_client_connect(mqtt_client.client,
          &mqtt_client.ipaddr, LWIP_IANA_PORT_SECURE_MQTT,
          mqtt_connection_cb, LWIP_CONST_CAST(void*, &mqtt_client),
          &mqtt_client.mqtt_connect_client_info);
    cyw43_arch_lwip_end();
   
    absolute_time_t timeout = make_timeout_time_ms(10000);
    while(!mqtt_client.connected && absolute_time_diff_us(get_absolute_time(), timeout) > 0) {
        sleep_ms(10);
        cyw43_arch_poll();
        printf(".");
    }
    printf("\n");
    if (!mqtt_client.connected) {
      printf("connect mqtt server:timeout\n");
      return 0;
    }
    
    printf("mqtt connected\n");
    // led : green
    ws2812_put_pixel_rgb(0x00ff00);
    ws2812_color = 0x00ff00;
    rotary_value = 1;
   
    sprintf(pub_buff, "%02d", rotary_value);
    mqtt_publish(mqtt_client.client, "ROTARY", pub_buff, 2, 1, 0, mqtt_pub_request_cb, &mqtt_client);

    uint8_t pre_rotary_value=rotary_value;
    uint32_t pre_ws2812_color=ws2812_color;
    
    while(1) {

        cyw43_arch_poll();
        sleep_ms(10);
        if (pre_rotary_value != rotary_value) {
          sprintf(pub_buff, "%02d", rotary_value);
          mqtt_publish(mqtt_client.client, "ROTARY", pub_buff, 2, 1, 0, mqtt_pub_request_cb, &mqtt_client);
          update_ws2812_color();
          pre_rotary_value = rotary_value;
        }
    }
    cyw43_arch_deinit();
    return 0;
}

  • picow_tls_mqtt.h


#ifndef __PICOW_TLS_CLIENT_H__
#define __PICOW_TLS_CLIENT_H__
#include "lwip/pbuf.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/dns.h"
#include "lwip/apps/mqtt.h"
#define RECV_BUFF_MAX_LEN   1024

typedef struct {
    mqtt_client_t *client;
    struct altcp_tls_config *tls_config;
    ip_addr_t ipaddr;
    struct mqtt_connect_client_info_t mqtt_connect_client_info;
    bool connected;
    uint8_t sub_topic[256];
    uint8_t sub_message[RECV_BUFF_MAX_LEN];
    uint16_t sub_message_offset;
    bool sub_message_recved;
} mqtt_client_instance_t;

#endif

  • picow_tls_cert.h


#define ca_cert "-----BEGIN CERTIFICATE-----\n\
...
-----END CERTIFICATE-----\n"

#define mqtt_client_cert "-----BEGIN CERTIFICATE-----\n\
...
-----END CERTIFICATE-----"

#define mqtt_client_key "-----BEGIN RSA PRIVATE KEY-----\n\
...
-----END RSA PRIVATE KEY-----"

  • CMakeLists.txt


# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico_w CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(picow_tls_mqtt C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(picow_tls_mqtt picow_tls_mqtt.c)

pico_set_program_name(picow_tls_mqtt "picow_tls_client")
pico_set_program_version(picow_tls_mqtt "0.1")

pico_enable_stdio_uart(picow_tls_mqtt 1)
pico_enable_stdio_usb(picow_tls_mqtt 0)

# Add the standard library to the build
target_link_libraries(picow_tls_mqtt
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_tls_mqtt PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
target_link_libraries(picow_tls_mqtt 
        pico_cyw43_arch_lwip_poll
        pico_lwip_mqtt
        pico_lwip_mbedtls
        pico_mbedtls
        )
add_subdirectory(rotary_encoder) 
target_link_libraries(picow_tls_mqtt
  rotary_encoder
)

add_subdirectory(ws2812) 
target_link_libraries(picow_tls_mqtt
ws2812
)

pico_add_extra_outputs(picow_tls_mqtt)

  • lwipopts.h


#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__


// Common settings used in most of the pico_w examples
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)

// allow override in some examples
#ifndef NO_SYS
#define NO_SYS                      1
#endif
// allow override in some examples
#ifndef LWIP_SOCKET
#define LWIP_SOCKET                 0
#endif
#if PICO_CYW43_ARCH_POLL
#define MEM_LIBC_MALLOC             1
#else
// MEM_LIBC_MALLOC is incompatible with non polling versions
#define MEM_LIBC_MALLOC             0
#endif
#define MEM_ALIGNMENT               4
#define MEM_SIZE                    4000
#define MEMP_NUM_TCP_SEG            32
#define MEMP_NUM_ARP_QUEUE          10
#define PBUF_POOL_SIZE              24
#define LWIP_ARP                    1
#define LWIP_ETHERNET               1
#define LWIP_ICMP                   1
#define LWIP_RAW                    1
#define TCP_WND                     (8 * TCP_MSS)
#define TCP_MSS                     1460
#define TCP_SND_BUF                 (8 * TCP_MSS)
#define TCP_SND_QUEUELEN            ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS))
#define LWIP_NETIF_STATUS_CALLBACK  1
#define LWIP_NETIF_LINK_CALLBACK    1
#define LWIP_NETIF_HOSTNAME         1
#define LWIP_NETCONN                0
#define MEM_STATS                   0
#define SYS_STATS                   0
#define MEMP_STATS                  0
#define LINK_STATS                  0
// #define ETH_PAD_SIZE                2
#define LWIP_CHKSUM_ALGORITHM       3
#define LWIP_DHCP                   1
#define LWIP_IPV4                   1
#define LWIP_TCP                    1
#define LWIP_UDP                    1
#define LWIP_DNS                    1
#define LWIP_TCP_KEEPALIVE          1
#define LWIP_NETIF_TX_SINGLE_PBUF   1
#define DHCP_DOES_ARP_CHECK         0
#define LWIP_DHCP_DOES_ACD_CHECK    0

#ifndef NDEBUG
#define LWIP_DEBUG                  1
#define LWIP_STATS                  1
#define LWIP_STATS_DISPLAY          1
#endif

#define ETHARP_DEBUG                LWIP_DBG_OFF
#define NETIF_DEBUG                 LWIP_DBG_OFF
#define PBUF_DEBUG                  LWIP_DBG_OFF
#define API_LIB_DEBUG               LWIP_DBG_OFF
#define API_MSG_DEBUG               LWIP_DBG_OFF
#define SOCKETS_DEBUG               LWIP_DBG_OFF
#define ICMP_DEBUG                  LWIP_DBG_OFF
#define INET_DEBUG                  LWIP_DBG_OFF
#define IP_DEBUG                    LWIP_DBG_OFF
#define IP_REASS_DEBUG              LWIP_DBG_OFF
#define RAW_DEBUG                   LWIP_DBG_OFF
#define MEM_DEBUG                   LWIP_DBG_OFF
#define MEMP_DEBUG                  LWIP_DBG_OFF
#define SYS_DEBUG                   LWIP_DBG_OFF
#define TCP_DEBUG                   LWIP_DBG_OFF
#define TCP_INPUT_DEBUG             LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG            LWIP_DBG_OFF
#define TCP_RTO_DEBUG               LWIP_DBG_OFF
#define TCP_CWND_DEBUG              LWIP_DBG_OFF
#define TCP_WND_DEBUG               LWIP_DBG_OFF
#define TCP_FR_DEBUG                LWIP_DBG_OFF
#define TCP_QLEN_DEBUG              LWIP_DBG_OFF
#define TCP_RST_DEBUG               LWIP_DBG_OFF
#define UDP_DEBUG                   LWIP_DBG_OFF
#define TCPIP_DEBUG                 LWIP_DBG_OFF
#define PPP_DEBUG                   LWIP_DBG_OFF
#define SLIP_DEBUG                  LWIP_DBG_OFF
#define DHCP_DEBUG                  LWIP_DBG_OFF

/*  in Pico-SDK above 1.5.0 */
#define MEMP_NUM_SYS_TIMEOUT            (LWIP_NUM_SYS_TIMEOUT_INTERNAL+1)

/* TCP WND must be at least 16 kb to match TLS record size
   or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */
// tls
#undef TCP_WND
#define TCP_WND  16384

#define LWIP_ALTCP               1
#define LWIP_ALTCP_TLS           1
#define LWIP_ALTCP_TLS_MBEDTLS   1

#define LWIP_DEBUG 1
#define ALTCP_MBEDTLS_DEBUG  LWIP_DBG_ON

/* set authmode */
#define ALTCP_MBEDTLS_AUTHMODE MBEDTLS_SSL_VERIFY_REQUIRED

#endif /* __LWIPOPTS_H__ */

  • mbedtls_config.h


/* Workaround for some mbedtls source files using INT_MAX without including limits.h */
#include < limits.h >

#define MBEDTLS_NO_PLATFORM_ENTROPY
#define MBEDTLS_ENTROPY_HARDWARE_ALT

#define MBEDTLS_SSL_OUT_CONTENT_LEN    2048

#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#define MBEDTLS_HAVE_TIME

#define MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_ECP_DP_SECP192R1_ENABLED
#define MBEDTLS_ECP_DP_SECP224R1_ENABLED
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_ECP_DP_SECP384R1_ENABLED
#define MBEDTLS_ECP_DP_SECP521R1_ENABLED
#define MBEDTLS_ECP_DP_SECP192K1_ENABLED
#define MBEDTLS_ECP_DP_SECP224K1_ENABLED
#define MBEDTLS_ECP_DP_SECP256K1_ENABLED
#define MBEDTLS_ECP_DP_BP256R1_ENABLED
#define MBEDTLS_ECP_DP_BP384R1_ENABLED
#define MBEDTLS_ECP_DP_BP512R1_ENABLED
#define MBEDTLS_ECP_DP_CURVE25519_ENABLED
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
#define MBEDTLS_PKCS1_V15
#define MBEDTLS_SHA256_SMALLER
#define MBEDTLS_SSL_SERVER_NAME_INDICATION
#define MBEDTLS_AES_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_CIPHER_C
#define MBEDTLS_CTR_DRBG_C
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_ERROR_C
#define MBEDTLS_MD_C
#define MBEDTLS_MD5_C
#define MBEDTLS_OID_C
#define MBEDTLS_PKCS5_C
#define MBEDTLS_PK_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_PLATFORM_C
#define MBEDTLS_RSA_C
#define MBEDTLS_SHA1_C
#define MBEDTLS_SHA224_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_SHA512_C
#define MBEDTLS_SSL_CLI_C
#define MBEDTLS_SSL_SRV_C
#define MBEDTLS_SSL_TLS_C
#define MBEDTLS_X509_CRT_PARSE_C
#define MBEDTLS_X509_USE_C
#define MBEDTLS_AES_FEWER_TABLES

/* TLS 1.2 */
#define MBEDTLS_SSL_PROTO_TLS1_2
#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#define MBEDTLS_GCM_C
#define MBEDTLS_ECDH_C
#define MBEDTLS_ECP_C
#define MBEDTLS_ECDSA_C
#define MBEDTLS_ASN1_WRITE_C

// The following is needed to parse a certificate
#define MBEDTLS_PEM_PARSE_C
#define MBEDTLS_BASE64_C

Rotary Encoder:

  • pio_rotary_encoder.c


#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"

#include "pio_rotary_encoder.h"
#include "rotary_encoder.pio.h"
#include "hardware/clocks.h"

void (*pio_callback)(uint8_t action);

void rotary_encoder_irq_handler() {
    if (pio_interrupt_get(RE_PIO, ROTARY_INT_NUM)) { // 1: clockwise, 2: countclockwise
        pio_interrupt_clear(RE_PIO, ROTARY_INT_NUM); 
        uint32_t act = pio_sm_get_blocking(RE_PIO, RE_SM_ROTARY);
        if (act == PIO_RE_CLOCKWISE || act == PIO_RE_COUNTERCLOCKWISE) { //PIO_RE_CLOCKWISE;
            pio_callback((uint8_t)act);
        } else {
            pio_callback(PIO_RE_UNKNOW);
        }
    }
    if (pio_interrupt_get(RE_PIO, SWITCH_INT_NUM)) {
        pio_interrupt_clear(RE_PIO, SWITCH_INT_NUM);
        uint8_t action  = PIO_RE_SWPRESS;

        pio_callback(action);
    }
    
    
}

void pio_rotary_switch_init(PIO pio, uint sm, uint in_base, uint freq) {
    gpio_pull_up(in_base);  // software pull-up

    pio_sm_config c;
    uint offset = pio_add_program(pio, &rotary_switch_program);

    c = rotary_switch_program_get_default_config(offset);
    
    pio_gpio_init(pio, in_base);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base,  1,false);
     
    sm_config_set_in_pins(&c, in_base);
    sm_config_set_in_shift(&c, false, false, 1);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    uint pio_irq_num = (pio==pio0) ? PIO0_IRQ_0: PIO1_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    pio_set_irq0_source_enabled(pio, pis_interrupt1, true);
    irq_add_shared_handler(pio_irq_num, rotary_encoder_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq_num, true);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void pio_rotary_encoder_init(PIO pio, uint sm, uint in_base, uint freq) {
    pio_sm_config c;
    uint offset = pio_add_program(pio, &rotary_encoder_program);

    c = rotary_encoder_program_get_default_config(offset);
    
    for (int i=0; i < 2; i++) pio_gpio_init(pio, in_base+i);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base,  2,false);
     
    sm_config_set_in_pins(&c, in_base);
    sm_config_set_in_shift(&c, false, false, 2);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    uint pio_irq_num = (pio==pio0) ? PIO0_IRQ_0: PIO1_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
  
    irq_add_shared_handler(pio_irq_num, rotary_encoder_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq_num, true);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}



void pio_rotary_encoder_default_init() {
    pio_rotary_switch_init(RE_PIO, RE_SM_SWITCH, RE_SW_PIN, 2000);
    pio_rotary_encoder_init(RE_PIO, RE_SM_ROTARY, RE_CLK_DT_PIN, RE_SM_FREQ);
}
void pio_rotary_encoder_set_callback(void (*cb)(uint8_t action)) {
    pio_callback=cb;
}

  • pio_rotary_encoder.h


#ifndef _PIO_ROTARY_ENCODER_H_
#define _PIO_ROTARY_ENCODER_H_
#include "hardware/pio.h"

#define RE_PIO          pio0
#define RE_CLK_DT_PIN  16
#define RE_SW_PIN      18
#define RE_PIO      pio0
#define RE_SM_ROTARY      (0)
#define RE_SM_SWITCH      (1)
#define RE_SM_FREQ  100000
enum PIO_RE_ACTION{
    PIO_RE_CLOCKWISE=1,
    PIO_RE_COUNTERCLOCKWISE=2,
    PIO_RE_SWPRESS=3,
    PIO_RE_UNKNOW=4
};

void pio_rotary_switch_init(PIO pio, uint sm, uint in_base, uint freq);
void pio_rotary_encoder_init(PIO pio, uint sm, uint in_base, uint freq);
void pio_rotary_encoder_default_init();
void pio_rotary_encoder_set_callback(void (*cb)(uint8_t action));


#endif 

  • rotary_encoder.pio


.define public ROTARY_INT_NUM 0
.define public SWITCH_INT_NUM 1
.program rotary_encoder
start:
in pins, 2 [31]
mov x, isr          ; CLK, DT present state
mov y, osr          ; CLK, DT previous state
jmp x!=y, rotary    ; not eaqual, rotary occure
jmp saveoldvalue    ; clear ISR
rotary:
set y, 0b01         ;
jmp x!=y, pos10
jmp check_dir
pos10:
set y, 0b10
jmp x!=y, saveoldvalue
jmp check_dir
saveoldvalue:
mov osr, isr 
in NULL, 32  ;; imporant to clear ISR
jmp start 

check_dir:
out y,1     ; previous state DT bit
mov osr,x   ; save oldvalue
in NULL, 32 ; clear ISR
in x, 1     ; shift in present state DT bit 
mov x, isr  ; present state DT bit
jmp x!=y counterclockwise
jmp clockwise

clockwise:
set y,1         ; clockwise: value = 1
jmp output
counterclockwise:
set y,2         ; counterclockwise: value = 2
output:
mov isr, y
push
irq wait ROTARY_INT_NUM
jmp start  

.program rotary_switch
wait 0 pin 0 [31]       ; Switch PIN is initially pulled up 
irq wait SWITCH_INT_NUM              ; [31] wait for button bounce
wait 1 pin 0 [31]



  • CMakeLists.txt


add_library(rotary_encoder INTERFACE)
pico_generate_pio_header(rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/rotary_encoder.pio)
target_sources(rotary_encoder INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.c
)

target_include_directories(rotary_encoder INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}

)

target_link_libraries(rotary_encoder INTERFACE
        hardware_pio
)

ws2812:

  • ws2812.c


#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "ws2812.pio.h"
#include "ws2812.h"

void ws2812_pio_init(PIO pio, uint sm, uint pin, float freq) {
    pio_gpio_init(pio, pin);
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);

    uint offset = pio_add_program(pio, &ws2812_program);

    pio_sm_config c = ws2812_program_get_default_config(offset);
    sm_config_set_sideset_pins(&c, pin);
    sm_config_set_out_shift(&c, false, true, 24);
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);

    int cycles_per_bit = 6;
    float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
    sm_config_set_clkdiv(&c, div);

    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void ws2812_put_pixel(uint32_t pixel_grb) {
    pio_sm_put_blocking(WS2812_PIO, WS2812_SM, pixel_grb << 8u);
}
void ws2812_put_pixel_rgb(uint32_t pixel_rgb) {
    uint32_t grb = ((pixel_rgb & 0xff00) >> 8) << 16 | ((pixel_rgb & 0xff0000) >> 16) << 8 | pixel_rgb &0xff; 
    ws2812_put_pixel(grb);
}
void ws2812_default_pio_init() {
    ws2812_pio_init(WS2812_PIO, WS2812_SM, WS2812_PIN, WS2812_PIO_FREQ);
}

  • ws2812.h


#ifndef _WS2812_H_
#define _WS2812_H_

#include "hardware/pio.h"

#define WS2812_PIO pio1
#define WS2812_SM   (1)
#define WS2812_PIN  (19)
#define WS2812_PIO_FREQ (800000)

void ws2812_pio_init(PIO pio, uint sm, uint pin, float freq);
void ws2812_default_pio_init();
void ws2812_put_pixel(uint32_t pixel_grb);
void ws2812_put_pixel_rgb(uint32_t pixel_rgb);
#endif

  • ws2812.pio


.program ws2812
.side_set 1

.wrap_target
bitloop:
    out x, 1       side 0 [1]
    jmp !x do_zero side 1 [1]
do_one:
    jmp  bitloop   side 1 [1]
do_zero:
    nop            side 0 [1]
.wrap

  • CMakeLists.txt


add_library(ws2812 INTERFACE)
pico_generate_pio_header(ws2812 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)
target_sources(ws2812 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/ws2812.c
)

target_include_directories(ws2812 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}

)

target_link_libraries(ws2812 INTERFACE
        hardware_pio
)


2023年11月16日 星期四

Raspberry Pi || NODE_RED || Pico W || TLS: Ep1. Getting Started - setup

本文章紀錄Raspberry Pi PicoW TLS Client連線,Server端建制NODE-RED在Raspberry Pi 3B+,並建立TLS TCP Flow來測試。


詳細步驟:
  • 使用rpi-imager建立Raspberry Pi 3+作業系統,使用不含Desktop。
    設定WiFi連線與enable SSH。
  • 安裝NODE-RED:
    ~$ sudo apt install build-essential git curl
    ~$ bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
  • 安裝self signed CA certificates:
    1.產生一組CA key:
     openssl genrsa -des3 -out ca.key 2048

    2.製作一組CA 憑證:
     openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

    3.製作 server key:
    openssl genrsa -out node_red_tls.key 2048

    4. 使用server key製作server憑證需求(certificate request):
     openssl req -new -out node_red_tls.csr -key node_red_tls.key

    5.製作server憑證:
    openssl x509 -req -in node_red_tls.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out node_red_tls.crt -days 360
  • 建立NODE-RED flow:

    輸入剛剛建立的憑證。
  • PicoW Client端:
    在lwipopts.h加入啟用mbedtls:

    加入ca.crt憑證。

/* TCP WND must be at least 16 kb to match TLS record size
   or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */
// tls
#undef TCP_WND
#define TCP_WND  16384

#define LWIP_ALTCP               1
#define LWIP_ALTCP_TLS           1
#define LWIP_ALTCP_TLS_MBEDTLS   1

#define LWIP_DEBUG 1
#define ALTCP_MBEDTLS_DEBUG  LWIP_DBG_ON

#define ALTCP_MBEDTLS_AUTHMODE MBEDTLS_SSL_VERIFY_REQUIRED

詳細步驟請參閱下列影片:


程式碼:

  • picow_tls_client.c

#include < stdio.h >
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "picow_tls_client.h"
#include "picow_tls_cert.h"

#define TCP_POLL_INTERVAL   10
#define LED_RED_PIN         13
#define LED_GREEN_PIN       14
#define LED_BLUE_PIN        15

#define WIFI_SSID           "your-ssid"
#define WIFI_PASSWORD       "your-password"
#define SERVER_IP           "your-server-ip"
#define SERVER_PORT         20000

static struct altcp_tls_config *tls_config = NULL;

err_t tcp_connect_close(TLS_CLIENT_T * tls_client) {
    tls_client->connected=false;
    tls_client->dns_found=false;
    return altcp_close(tls_client->tpcb);
}

static int32_t buff_remain_len=0;

err_t tcp_recv_cb(void *arg, struct altcp_pcb *tpcb, struct pbuf *p, err_t err) {
    TLS_CLIENT_T *tls_client = (TLS_CLIENT_T*)arg;
    if (!p) {
        printf("pbuf null\n");
        return tcp_connect_close(tls_client);
    }
    
    if (p->tot_len > 0) {
        // for test only
        int32_t len;
        int32_t offset=0;
        buff_remain_len = p->tot_len;
        while (buff_remain_len > 0) {
            len = buff_remain_len > RECV_BUFF_MAX_LEN ? RECV_BUFF_MAX_LEN : buff_remain_len;
            pbuf_copy_partial(p, tls_client->recv_buff, len,offset);
            tls_client->recv_buff[len]=0;
            printf("%s\n", tls_client->recv_buff);
            buff_remain_len -= len;
            offset+=len;
        } 
        
       altcp_recved(tpcb, p->tot_len);
    }
    gpio_put(LED_GREEN_PIN, !gpio_get(LED_GREEN_PIN));
    pbuf_free(p);
    return ERR_OK;
}

err_t tcp_sent_cb(void *arg, struct altcp_pcb *tpcb, u16_t len) {
    return ERR_OK;
}

err_t tcp_poll_cb(void *arg, struct altcp_pcb *tpcb) {
    TLS_CLIENT_T *tls_client = (TLS_CLIENT_T*)arg;
    return tcp_connect_close(tls_client);
}

void tcp_err_cb(void *arg, err_t err) {
    printf("tcp error:%d\n");
    tcp_connect_close(arg);
}

err_t tcp_connected_cb(void *arg, struct altcp_pcb *tpcb, err_t err) {
    TLS_CLIENT_T *tls_client = (TLS_CLIENT_T*)arg;
    if (err == ERR_OK) {
        printf("tls server connected \n");
        tls_client->connected = true;
    }
    return err;
}

void tls_client_dns_found(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
    TLS_CLIENT_T *tls_client = (TLS_CLIENT_T*)callback_arg;
    if (ipaddr)
    {
        printf("DNS resolving complete\n");
        tls_client->dns_found=true;
        tls_client->server_addr = *ipaddr;
    }
    else
    {
        printf("error resolving hostname %s\n", name);
        tls_client->dns_found=false;
    }
}

int main()
{
    
    int wifi_stat;
    err_t tcp_stat;
    stdio_init_all();
    printf("Starting \n");

    if (cyw43_arch_init()) {
        printf("cyw43 init error\n");
        return 0;
    }
    gpio_init_mask(1 << LED_BLUE_PIN | 1 << LED_GREEN_PIN | 1 << LED_RED_PIN);
    gpio_set_dir_out_masked(1 << LED_BLUE_PIN | 1 << LED_GREEN_PIN | 1 << LED_RED_PIN);
    
    tls_config = altcp_tls_create_config_client(ca_cert, sizeof(ca_cert));
    //tls_config = altcp_tls_create_config_client(NULL, 0);
    assert(tls_config);

    //led: red
    gpio_put_masked(1 << LED_BLUE_PIN | 1 << LED_GREEN_PIN | 1 << LED_RED_PIN, 0);
    gpio_put(LED_RED_PIN, true);

    // connect to WiFi
    cyw43_arch_enable_sta_mode();
    wifi_stat = cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000);
    if (wifi_stat) {
        printf("wifi connect error:%d\n", wifi_stat);
        return 0;
    }
    printf("Wifi connected\n");
    //led: blue
    gpio_put_masked(1 << LED_BLUE_PIN | 1 << LED_GREEN_PIN | 1 << LED_RED_PIN, 0);
    gpio_put(LED_BLUE_PIN, true);

    // connect to tcp server
    TLS_CLIENT_T* tls_client = (TLS_CLIENT_T*)calloc(1, sizeof(TLS_CLIENT_T));
    // set callback
    tls_client->tpcb = altcp_tls_new(tls_config, IPADDR_TYPE_ANY);
    altcp_arg(tls_client->tpcb, tls_client);
    altcp_sent(tls_client->tpcb, tcp_sent_cb);
    altcp_recv(tls_client->tpcb, tcp_recv_cb);
    altcp_poll(tls_client->tpcb, tcp_poll_cb, TCP_POLL_INTERVAL);
    altcp_err(tls_client->tpcb, tcp_err_cb);
    /*
    // if DNS query
    mbedtls_ssl_set_hostname(altcp_tls_context(tcp_client.tpcb), SERVER_NAME);
    tls_client->dns_found=false;
    cyw43_arch_lwip_begin();
    tcp_stat = dns_gethostbyname(SERVER_NAME, &tls_client->server_addr, tls_client_dns_found, &tls_client);     
    while(!tls_client->dns_found) {
         cyw43_arch_poll();
        sleep_ms(10);
    }
    */
   // if not by dns
   ipaddr_aton(SERVER_IP, &tls_client->server_addr);
   
   // connect to server;
    tls_client->connected = false;
    tcp_stat = altcp_connect(tls_client->tpcb, &tls_client->server_addr, SERVER_PORT, tcp_connected_cb);
    
    if (tcp_stat != ERR_OK) {
        printf("connect TCP server error:%d\n", tcp_stat);
        return 0;
    }
    absolute_time_t timeout = make_timeout_time_ms(20000);
    while(!tls_client->connected && absolute_time_diff_us(get_absolute_time(), timeout)>0){
        printf(".");
        cyw43_arch_poll();
        sleep_ms(10);
    } 
    printf("\n");
    if (!tls_client->connected) {
        printf("connection timeout\n");
        free(tls_client);
        return 0;
    }
    // led : green
    gpio_put_masked(1 << LED_BLUE_PIN | 1 << LED_GREEN_PIN | 1 << LED_RED_PIN, 0);
    gpio_put(LED_GREEN_PIN, true);

    uint8_t buff[100];
    uint32_t idx=0;
    while(1) {
        sprintf(buff, "test from PicoW:%020d", idx++);
        altcp_write(tls_client->tpcb, buff, strlen(buff), TCP_WRITE_FLAG_COPY);
        cyw43_arch_poll();
        cyw43_arch_wait_for_work_until(make_timeout_time_ms(1000));
        //sleep_ms(50);
    }
    altcp_close(tls_client->tpcb);
    free(tls_client);
    cyw43_arch_deinit();

    return 0;
}

  • CMakeLists.txt

# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico_w CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(picow_tls_client C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(picow_tls_client picow_tls_client.c )

pico_set_program_name(picow_tls_client "picow_tls_client")
pico_set_program_version(picow_tls_client "0.1")

pico_enable_stdio_uart(picow_tls_client 1)
pico_enable_stdio_usb(picow_tls_client 0)

# Add the standard library to the build
target_link_libraries(picow_tls_client
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_tls_client PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
target_link_libraries(picow_tls_client 
        pico_cyw43_arch_lwip_poll
        pico_lwip_mbedtls
        pico_mbedtls
        )

pico_add_extra_outputs(picow_tls_client)

  • picow_tls_client.h

#ifndef __PICOW_TLS_CLIENT_H__
#define __PICOW_TLS_CLIENT_H__
#include "lwip/pbuf.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/dns.h"
#define RECV_BUFF_MAX_LEN   1024
typedef struct {
    struct altcp_pcb *tpcb;
    ip_addr_t server_addr;
    bool connected;
    bool dns_found;
    uint8_t recv_buff[RECV_BUFF_MAX_LEN+1];
} TLS_CLIENT_T;


#endif
  • lwipopts.h

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__


// Common settings used in most of the pico_w examples
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)

// allow override in some examples
#ifndef NO_SYS
#define NO_SYS                      1
#endif
// allow override in some examples
#ifndef LWIP_SOCKET
#define LWIP_SOCKET                 0
#endif
#if PICO_CYW43_ARCH_POLL
#define MEM_LIBC_MALLOC             1
#else
// MEM_LIBC_MALLOC is incompatible with non polling versions
#define MEM_LIBC_MALLOC             0
#endif
#define MEM_ALIGNMENT               4
#define MEM_SIZE                    4000
#define MEMP_NUM_TCP_SEG            32
#define MEMP_NUM_ARP_QUEUE          10
#define PBUF_POOL_SIZE              24
#define LWIP_ARP                    1
#define LWIP_ETHERNET               1
#define LWIP_ICMP                   1
#define LWIP_RAW                    1
#define TCP_WND                     (8 * TCP_MSS)
#define TCP_MSS                     1460
#define TCP_SND_BUF                 (8 * TCP_MSS)
#define TCP_SND_QUEUELEN            ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS))
#define LWIP_NETIF_STATUS_CALLBACK  1
#define LWIP_NETIF_LINK_CALLBACK    1
#define LWIP_NETIF_HOSTNAME         1
#define LWIP_NETCONN                0
#define MEM_STATS                   0
#define SYS_STATS                   0
#define MEMP_STATS                  0
#define LINK_STATS                  0
// #define ETH_PAD_SIZE                2
#define LWIP_CHKSUM_ALGORITHM       3
#define LWIP_DHCP                   1
#define LWIP_IPV4                   1
#define LWIP_TCP                    1
#define LWIP_UDP                    1
#define LWIP_DNS                    1
#define LWIP_TCP_KEEPALIVE          1
#define LWIP_NETIF_TX_SINGLE_PBUF   1
#define DHCP_DOES_ARP_CHECK         0
#define LWIP_DHCP_DOES_ACD_CHECK    0

#ifndef NDEBUG
#define LWIP_DEBUG                  1
#define LWIP_STATS                  1
#define LWIP_STATS_DISPLAY          1
#endif

#define ETHARP_DEBUG                LWIP_DBG_OFF
#define NETIF_DEBUG                 LWIP_DBG_OFF
#define PBUF_DEBUG                  LWIP_DBG_OFF
#define API_LIB_DEBUG               LWIP_DBG_OFF
#define API_MSG_DEBUG               LWIP_DBG_OFF
#define SOCKETS_DEBUG               LWIP_DBG_OFF
#define ICMP_DEBUG                  LWIP_DBG_OFF
#define INET_DEBUG                  LWIP_DBG_OFF
#define IP_DEBUG                    LWIP_DBG_OFF
#define IP_REASS_DEBUG              LWIP_DBG_OFF
#define RAW_DEBUG                   LWIP_DBG_OFF
#define MEM_DEBUG                   LWIP_DBG_OFF
#define MEMP_DEBUG                  LWIP_DBG_OFF
#define SYS_DEBUG                   LWIP_DBG_OFF
#define TCP_DEBUG                   LWIP_DBG_OFF
#define TCP_INPUT_DEBUG             LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG            LWIP_DBG_OFF
#define TCP_RTO_DEBUG               LWIP_DBG_OFF
#define TCP_CWND_DEBUG              LWIP_DBG_OFF
#define TCP_WND_DEBUG               LWIP_DBG_OFF
#define TCP_FR_DEBUG                LWIP_DBG_OFF
#define TCP_QLEN_DEBUG              LWIP_DBG_OFF
#define TCP_RST_DEBUG               LWIP_DBG_OFF
#define UDP_DEBUG                   LWIP_DBG_OFF
#define TCPIP_DEBUG                 LWIP_DBG_OFF
#define PPP_DEBUG                   LWIP_DBG_OFF
#define SLIP_DEBUG                  LWIP_DBG_OFF
#define DHCP_DEBUG                  LWIP_DBG_OFF

/* TCP WND must be at least 16 kb to match TLS record size
   or you will get a warning "altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!" */
// tls
#undef TCP_WND
#define TCP_WND  16384

#define LWIP_ALTCP               1
#define LWIP_ALTCP_TLS           1
#define LWIP_ALTCP_TLS_MBEDTLS   1

#define LWIP_DEBUG 1
#define ALTCP_MBEDTLS_DEBUG  LWIP_DBG_ON

#define ALTCP_MBEDTLS_AUTHMODE MBEDTLS_SSL_VERIFY_REQUIRED

#endif /* __LWIPOPTS_H__ */

  • mbedtls_config.h

/* Workaround for some mbedtls source files using INT_MAX without including limits.h */
#include < limits.h >

#define MBEDTLS_NO_PLATFORM_ENTROPY
#define MBEDTLS_ENTROPY_HARDWARE_ALT

#define MBEDTLS_SSL_OUT_CONTENT_LEN    2048

#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#define MBEDTLS_HAVE_TIME

#define MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_ECP_DP_SECP192R1_ENABLED
#define MBEDTLS_ECP_DP_SECP224R1_ENABLED
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_ECP_DP_SECP384R1_ENABLED
#define MBEDTLS_ECP_DP_SECP521R1_ENABLED
#define MBEDTLS_ECP_DP_SECP192K1_ENABLED
#define MBEDTLS_ECP_DP_SECP224K1_ENABLED
#define MBEDTLS_ECP_DP_SECP256K1_ENABLED
#define MBEDTLS_ECP_DP_BP256R1_ENABLED
#define MBEDTLS_ECP_DP_BP384R1_ENABLED
#define MBEDTLS_ECP_DP_BP512R1_ENABLED
#define MBEDTLS_ECP_DP_CURVE25519_ENABLED
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
#define MBEDTLS_PKCS1_V15
#define MBEDTLS_SHA256_SMALLER
#define MBEDTLS_SSL_SERVER_NAME_INDICATION
#define MBEDTLS_AES_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_CIPHER_C
#define MBEDTLS_CTR_DRBG_C
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_ERROR_C
#define MBEDTLS_MD_C
#define MBEDTLS_MD5_C
#define MBEDTLS_OID_C
#define MBEDTLS_PKCS5_C
#define MBEDTLS_PK_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_PLATFORM_C
#define MBEDTLS_RSA_C
#define MBEDTLS_SHA1_C
#define MBEDTLS_SHA224_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_SHA512_C
#define MBEDTLS_SSL_CLI_C
#define MBEDTLS_SSL_SRV_C
#define MBEDTLS_SSL_TLS_C
#define MBEDTLS_X509_CRT_PARSE_C
#define MBEDTLS_X509_USE_C
#define MBEDTLS_AES_FEWER_TABLES

/* TLS 1.2 */
#define MBEDTLS_SSL_PROTO_TLS1_2
#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#define MBEDTLS_GCM_C
#define MBEDTLS_ECDH_C
#define MBEDTLS_ECP_C
#define MBEDTLS_ECDSA_C
#define MBEDTLS_ASN1_WRITE_C

// The following is needed to parse a certificate
#define MBEDTLS_PEM_PARSE_C
#define MBEDTLS_BASE64_C

  • picow_tls_cert.h

#define ca_cert "-----BEGIN CERTIFICATE-----\n\
.
.
.
-----END CERTIFICATE-----\n"


2023年11月7日 星期二

[Raspberry Pi Pico c-sdk] VL53L0X Time-of-Flight Ranging Sensor (Polling & Interrupt Ranging mode) || RP2040

本文章 以VL53L0X API為基礎,實驗在RP2040下測試距離量測:

  1. polling mode: Single ranging or Continuous ranging。
  2. interrupt mode: ready for new new measurement。
接線圖:


一、實作步驟:

  1. download VL53L0X API from ST website: https://www.st.com/en/embedded-software/stsw-img005.html
  2. make a folder vl53l0x_api_rp2040 in project folder
  3. copy api/core to project folder vl53l0x_api_rp2040/core
  4. copy platform/inc/vl53l0x_platform.h, vl53l0x_platform_log.h, vl53l0x_types.h to vl53l0x_api_rp2040/platform/inc folder.
  5. copy platform/src/vl53lx_platform.c to vl53l0x_api_rp2040/platform/src folder
資料夾結構如下圖所示:


二、修改部份

  1. 實作src/vl53l0x_rp2040.c and inc/vl53l0x_rp2040.h, 詳細程式碼附於文章末尾。
  2. vl53l0x_platform.h 與 vl53l0x_platform.c稍微修改。
  • vl53l0x_platform.h:
    #include "vl53l0x_def.h"
    #include "vl53l0x_platform_log.h"
    //#include "vl53l0x_i2c_platform.h"

  • vl53l0x_platform.c:
    include files:
    #include "vl53l0x_platform.h"
    //#include "vl53l0x_i2c_platform.h"
    #include "vl53l0x_rp2040.h"

    #include "vl53l0x_api.h"
    //#include <Windows.h>
    #include "pico/stdlib.h"

    檔案末尾, 修改VL53L0X_PollingDelay():
VL53L0X_Error VL53L0X_PollingDelay(VL53L0X_DEV Dev){
    VL53L0X_Error status = VL53L0X_ERROR_NONE;

    busy_wait_ms(1);
    
    /*
    LOG_FUNCTION_START("");

    const DWORD cTimeout_ms = 1;
    HANDLE hEvent = CreateEvent(0, TRUE, FALSE, 0);
    if(hEvent != NULL)
    {
        WaitForSingleObject(hEvent,cTimeout_ms);
    }

    LOG_FUNCTION_END(status);
    */
    return status;
}

三、實測影片:




四、程式碼:


  • vl43l0x_rp2040.c


#include "stdio.h"   
#include "pico/stdlib.h"

#include "vl53l0x_rp2040.h"
#include "string.h"
#include "vl53l0x_api.h"

#define STATUS_OK              0x00
#define STATUS_FAIL            0x01

i2c_inst_t* vl53l0x_i2c_port = i2c_default;
uint vl53l0x_i2c_sda = PICO_DEFAULT_I2C_SDA_PIN;
uint vl53l0x_i2c_scl = PICO_DEFAULT_I2C_SCL_PIN;

VL53L0X_Error VL53L0X_dev_i2c_default_initialise(VL53L0X_Dev_t *pDevice, uint32_t RangeProfile) {
    VL53L0X_Error Status;
    i2c_init(vl53l0x_i2c_port, 400 * 1000);
    gpio_set_function(vl53l0x_i2c_sda, GPIO_FUNC_I2C);
    gpio_set_function(vl53l0x_i2c_scl, GPIO_FUNC_I2C);
    gpio_pull_up(vl53l0x_i2c_sda);
    gpio_pull_up(vl53l0x_i2c_scl);

    Status = VL53L0X_device_initizlise(pDevice, RangeProfile);

    return Status;
}

VL53L0X_Error VL53L0X_dev_i2c_initialise(VL53L0X_Dev_t *pDevice,
    i2c_inst_t* i2c_port, uint sda, uint scl, uint16_t i2c_speed_k, uint32_t RangeProfile)
{
    VL53L0X_Error Status;
    vl53l0x_i2c_port = i2c_port;
    vl53l0x_i2c_sda = sda;
    vl53l0x_i2c_scl = scl;

    i2c_init(vl53l0x_i2c_port, i2c_speed_k * 1000);
    gpio_set_function(vl53l0x_i2c_sda, GPIO_FUNC_I2C);
    gpio_set_function(vl53l0x_i2c_scl, GPIO_FUNC_I2C);
    gpio_pull_up(vl53l0x_i2c_sda);
    gpio_pull_up(vl53l0x_i2c_scl);

    Status = VL53L0X_device_initizlise(pDevice, RangeProfile);
    return Status;
}

VL53L0X_Error VL53L0X_device_initizlise(VL53L0X_Dev_t *pDevice, uint32_t RangeProfile) {
    VL53L0X_Error Status = VL53L0X_ERROR_NONE;
    int i;
    uint32_t refSpadCount;
    uint8_t isApertureSpads;
    uint8_t VhvSettings;
    uint8_t PhaseCal;

    Status = VL53L0X_DataInit(pDevice); 
    if(Status != VL53L0X_ERROR_NONE) return Status;

    Status = VL53L0X_StaticInit(pDevice); // Device Initialization
    if(Status != VL53L0X_ERROR_NONE) return Status;
   
    Status = VL53L0X_PerformRefCalibration(pDevice,
        	&VhvSettings, &PhaseCal); // Device Initialization
    if(Status != VL53L0X_ERROR_NONE) return Status;
    
    Status = VL53L0X_PerformRefSpadManagement(pDevice,
        		&refSpadCount, &isApertureSpads); // Device Initialization
    if(Status != VL53L0X_ERROR_NONE) return Status;
    
     Status = VL53L0X_SetLimitCheckEnable(pDevice,
        	VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, 1);
    if(Status != VL53L0X_ERROR_NONE) return Status;

    Status = VL53L0X_SetLimitCheckEnable(pDevice,
        		VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, 1);
    if(Status != VL53L0X_ERROR_NONE) return Status;	
   
    Status = VL53L0X_SetLimitCheckValue(pDevice,
            VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,
            (FixPoint1616_t)(0.1*65536));
	if(Status != VL53L0X_ERROR_NONE) return Status;

    Status = VL53L0X_SetLimitCheckValue(pDevice,
            VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE,
            (FixPoint1616_t)(60*65536));			
    if(Status != VL53L0X_ERROR_NONE) return Status;
    
    Status = VL53L0X_SetMeasurementTimingBudgetMicroSeconds(pDevice,
        		RangeProfile);
	if(Status != VL53L0X_ERROR_NONE) return Status;
	   
    Status = VL53L0X_SetVcselPulsePeriod(pDevice, 
		        VL53L0X_VCSEL_PERIOD_PRE_RANGE, 18);
    if(Status != VL53L0X_ERROR_NONE) return Status;

    Status = VL53L0X_SetVcselPulsePeriod(pDevice, 
	        VL53L0X_VCSEL_PERIOD_FINAL_RANGE, 14);
    if(Status != VL53L0X_ERROR_NONE) return Status;

    return Status;
}

int32_t VL53L0X_write_multi(uint8_t address, uint8_t index, uint8_t *pdata, int32_t count)
{
    int32_t status = STATUS_OK;
    uint8_t i2c_buff[count+1];
    i2c_buff[0] = index;
    memcpy(i2c_buff+1, pdata, count);
    if ( i2c_write_blocking(vl53l0x_i2c_port, address, i2c_buff, count+1, false) == PICO_ERROR_GENERIC) {
        status = STATUS_FAIL;
    }
    return status;
}

int32_t VL53L0X_read_multi(uint8_t address, uint8_t index, uint8_t *pdata, int32_t count)
{
    int32_t status = STATUS_OK;

    int i2c_ret = i2c_write_blocking(vl53l0x_i2c_port, address, &index, 1, true);
    if (i2c_ret == PICO_ERROR_GENERIC) return STATUS_FAIL;
    i2c_ret = i2c_read_blocking(vl53l0x_i2c_port, address, pdata, count, false);
    if (i2c_ret == PICO_ERROR_GENERIC) return STATUS_FAIL;
    return status;
}


int32_t VL53L0X_write_byte(uint8_t address, uint8_t index, uint8_t data)
{
    int32_t status = STATUS_OK;

    status = VL53L0X_write_multi(address, index, &data, 1);

    return status;

}


int32_t VL53L0X_write_word(uint8_t address, uint8_t index, uint16_t data)
{
    int32_t status = STATUS_OK;
    uint8_t  buffer[BYTES_PER_WORD];
    // Split 16-bit word into MS and LS uint8_t
    buffer[0] = (uint8_t)(data >> 8);
    buffer[1] = (uint8_t)(data &  0x00FF);
    status = VL53L0X_write_multi(address, index, buffer, BYTES_PER_WORD);
    return status;

}


int32_t VL53L0X_write_dword(uint8_t address, uint8_t index, uint32_t data)
{
    int32_t status = STATUS_OK;
    uint8_t  buffer[BYTES_PER_DWORD];
    // Split 32-bit word into MS ... LS bytes
    buffer[0] = (uint8_t) (data >> 24);
    buffer[1] = (uint8_t)((data &  0x00FF0000) >> 16);
    buffer[2] = (uint8_t)((data &  0x0000FF00) >> 8);
    buffer[3] = (uint8_t) (data &  0x000000FF);
    status = VL53L0X_write_multi(address, index, buffer, BYTES_PER_DWORD);
    return status;
}


int32_t VL53L0X_read_byte(uint8_t address, uint8_t index, uint8_t *pdata)
{
    int32_t status = STATUS_OK;
    int32_t cbyte_count = 1;
    status = VL53L0X_read_multi(address, index, pdata, cbyte_count);
    return status;
}

int32_t VL53L0X_read_word(uint8_t address, uint8_t index, uint16_t *pdata)
{
    int32_t  status = STATUS_OK;
    	uint8_t  buffer[BYTES_PER_WORD];
    
    status = VL53L0X_read_multi(address, index, buffer, BYTES_PER_WORD);

	*pdata = ((uint16_t)buffer[0] << 8) + (uint16_t)buffer[1];

    return status;
}

int32_t VL53L0X_read_dword(uint8_t address, uint8_t index, uint32_t *pdata)
{
    int32_t status = STATUS_OK;
	uint8_t  buffer[BYTES_PER_DWORD];
    status = VL53L0X_read_multi(address, index, buffer, BYTES_PER_DWORD);
    *pdata = ((uint32_t)buffer[0] << 24) + ((uint32_t)buffer[1] << 16) + ((uint32_t)buffer[2] << 8) + (uint32_t)buffer[3];
    return status;
}


VL53L0X_Error VL53L0X_SingleRanging(VL53L0X_Dev_t *pDevice, uint16_t *MeasuredData) {
    VL53L0X_Error Status = VL53L0X_ERROR_NONE;
    VL53L0X_RangingMeasurementData_t RangingMeasurementData;
    
    *MeasuredData=0; 
    Status = VL53L0X_SetDeviceMode(pDevice, VL53L0X_DEVICEMODE_SINGLE_RANGING); // Setup in single ranging mode
    if(Status != VL53L0X_ERROR_NONE) return Status;

    Status = VL53L0X_PerformSingleRangingMeasurement(pDevice,
                &RangingMeasurementData);
    if (Status == VL53L0X_ERROR_NONE && RangingMeasurementData.RangeStatus == 0) {
        *MeasuredData = RangingMeasurementData.RangeMilliMeter;
    } else {
        Status = VL53L0X_ERROR_RANGE_ERROR;
    }
    /* for accuracy average several samples
    uint32_t ranging=0;
    uint32_t valid_count=0;
    int i;
    for(i=0; i<10; i++){
        Status = VL53L0X_PerformSingleRangingMeasurement(pDevice,
                &RangingMeasurementData);
        if (Status == VL53L0X_ERROR_NONE && RangingMeasurementData.RangeStatus == 0) {
            ranging += RangingMeasurementData.RangeMilliMeter;
            valid_count++;
        }
        
        if (Status != VL53L0X_ERROR_NONE) break;
    }
    
    if (valid_count == 0) {
        Status = VL53L0X_ERROR_RANGE_ERROR;
    } else {
        *MeasuredData = ranging/valid_count;
    }
    */ 
    return Status;
}

VL53L0X_Error WaitMeasurementDataReady(VL53L0X_Dev_t *pDevice) {
    VL53L0X_Error Status = VL53L0X_ERROR_NONE;
    uint8_t dataReady=0;
    absolute_time_t timeout = make_timeout_time_ms(200);
    do { 
        Status = VL53L0X_GetMeasurementDataReady(pDevice, &dataReady);
        if ((dataReady == 0x01) || Status != VL53L0X_ERROR_NONE) {
            break;
        }
    } while (absolute_time_diff_us(get_absolute_time(), timeout) > 0);
    if (!dataReady) Status = VL53L0X_ERROR_TIME_OUT;
    return Status;
}

VL53L0X_Error WaitStopCompleted(VL53L0X_Dev_t *pDevice) {
    VL53L0X_Error Status = VL53L0X_ERROR_NONE;
    uint32_t StopCompleted=1;
    absolute_time_t timeout = make_timeout_time_ms(200);
    do {
        Status = VL53L0X_GetStopCompletedStatus(pDevice, &StopCompleted);
        if ((StopCompleted == 0x00) || Status != VL53L0X_ERROR_NONE) {
            break;
        }
    } while (absolute_time_diff_us(get_absolute_time(), timeout) > 0);

    if (StopCompleted) {
         Status = VL53L0X_ERROR_TIME_OUT;
    }
    return Status;
}

VL53L0X_Error VL53L0X_ContinuousRanging(VL53L0X_Dev_t *pDevice, uint16_t *MeasuredData, uint16_t RangeCount, uint16_t *validCount){
    VL53L0X_Error Status = VL53L0X_ERROR_NONE;
    VL53L0X_RangingMeasurementData_t    RangingMeasurementData;

    Status = VL53L0X_SetDeviceMode(pDevice, VL53L0X_DEVICEMODE_CONTINUOUS_RANGING); 
    if (Status != VL53L0X_ERROR_NONE) return Status;

    Status = VL53L0X_StartMeasurement(pDevice);
    if (Status != VL53L0X_ERROR_NONE) return Status;
    *validCount=0;
    uint16_t vCount=0;
    for (int i=0; i < RangeCount; i++) {

        Status = WaitMeasurementDataReady(pDevice);
        if (Status != VL53L0X_ERROR_NONE) break;

        Status = VL53L0X_GetRangingMeasurementData(pDevice, &RangingMeasurementData);
        if (Status == VL53L0X_ERROR_NONE ) { 
            if (RangingMeasurementData.RangeStatus == 0) {
                MeasuredData[vCount++] = RangingMeasurementData.RangeMilliMeter;
                //printf("valid:%d, m:%d \n",vCount, RangingMeasurementData.RangeMilliMeter);
            // Clear the interrupt
            }
            VL53L0X_ClearInterruptMask(pDevice, VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
            VL53L0X_PollingDelay(pDevice);
        }
    }
    *validCount = vCount;

    Status = VL53L0X_StopMeasurement(pDevice);
    if (Status != VL53L0X_ERROR_NONE) return Status;

    Status = WaitStopCompleted(pDevice);
    if (Status != VL53L0X_ERROR_NONE) return Status;

    Status = VL53L0X_ClearInterruptMask(pDevice, VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);

    return Status;
}


  • vl43l0x_rp2040.h


#ifndef _VL53L0X_RP2040_H_
#define _VL53L0X_RP2040_H_

#ifdef __cplusplus
extern "C" {
#endif
#include "hardware/i2c.h"
#include "vl53l0x_platform.h"

#define    BYTES_PER_WORD        2
#define    BYTES_PER_DWORD       4

enum {
    VL53L0X_DEFAULT_MODE    =   30000,
    VL53L0X_HIGHT_ACCURACY  =   200000,
    VL53L0X_LONG_RANGE      =   33000,
    VL53L0X_HIGH_SPEED      =   20000
} RANGE_PROFILE;

int32_t VL53L0X_write_multi(uint8_t address, uint8_t index, uint8_t  *pdata, int32_t count);
int32_t VL53L0X_read_multi(uint8_t address,  uint8_t index, uint8_t  *pdata, int32_t count);
int32_t VL53L0X_write_byte(uint8_t address,  uint8_t index, uint8_t   data);
int32_t VL53L0X_write_word(uint8_t address,  uint8_t index, uint16_t  data);
int32_t VL53L0X_write_dword(uint8_t address, uint8_t index, uint32_t  data);
int32_t VL53L0X_read_byte(uint8_t address,  uint8_t index, uint8_t  *pdata);
int32_t VL53L0X_read_word(uint8_t address,  uint8_t index, uint16_t *pdata);
int32_t VL53L0X_read_dword(uint8_t address, uint8_t index, uint32_t *pdata);

VL53L0X_Error VL53L0X_device_initizlise(VL53L0X_Dev_t *pDevice, uint32_t RangeProfile);

VL53L0X_Error VL53L0X_dev_i2c_default_initialise(VL53L0X_Dev_t *pDevice, uint32_t RangeProfile);
VL53L0X_Error VL53L0X_dev_i2c_initialise(VL53L0X_Dev_t *pDevice,
    i2c_inst_t* i2c_port, uint sda, uint scl, uint16_t i2c_speed_k, uint32_t RangeProfile);
void vl53l0x_print_device_info(VL53L0X_Dev_t *pDevice);

VL53L0X_Error VL53L0X_SingleRanging(VL53L0X_Dev_t *pDevice, uint16_t *MeasureData);
VL53L0X_Error VL53L0X_ContinuousRanging(VL53L0X_Dev_t *pDevice, uint16_t *MeasuredData, uint16_t RangeCount, uint16_t *validCount);


#ifdef __cplusplus
}
#endif

#endif //_VL53L0X_RP2040_H_

  • CMakeLists.txt(vl53l0x_api_rp2040)


add_library(vl53l0x_api_rp2040 INTERFACE)
target_sources(vl53l0x_api_rp2040 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/core/src/vl53l0x_api_calibration.c
    ${CMAKE_CURRENT_LIST_DIR}/core/src/vl53l0x_api_core.c
    ${CMAKE_CURRENT_LIST_DIR}/core/src/vl53l0x_api_ranging.c
    ${CMAKE_CURRENT_LIST_DIR}/core/src/vl53l0x_api_strings.c
    ${CMAKE_CURRENT_LIST_DIR}/core/src/vl53l0x_api.c 
    ${CMAKE_CURRENT_LIST_DIR}/platform/src/vl53l0x_rp2040.c
    ${CMAKE_CURRENT_LIST_DIR}/platform/src/vl53l0x_platform.c 
)

target_include_directories(vl53l0x_api_rp2040 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/core/inc
    ${CMAKE_CURRENT_LIST_DIR}/core/src
    ${CMAKE_CURRENT_LIST_DIR}/platform/inc
    ${CMAKE_CURRENT_LIST_DIR}/platform/crc
)

target_link_libraries(vl53l0x_api_rp2040 INTERFACE
        pico_stdlib
        hardware_i2c
)

  • pico_vl53l0x.c(main test file)


#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"

#include "vl53l0x_api.h"
#include "vl53l0x_rp2040.h"
#include "pico/multicore.h"

VL53L0X_RangingMeasurementData_t gRangingData;
VL53L0X_Dev_t gVL53L0XDevice;

#define VL53L0X_GPIO_IRQ    16
#define RED_LED_PIN         15
#define GREEN_LED_PIN       14
#define BLUE_LED_PIN        13

volatile bool vl53l0x_irq_data_ready=false;

void gpioirq_cb(uint gpio, uint32_t event_mask) {
    VL53L0X_Error Status;
    uint8_t data_ready=0;
    if (gpio == VL53L0X_GPIO_IRQ) {
        if (event_mask & GPIO_IRQ_EDGE_FALL) {
            gpio_acknowledge_irq(VL53L0X_GPIO_IRQ, GPIO_IRQ_EDGE_RISE);
            vl53l0x_irq_data_ready=true;
        }
    }
}

void core1__interrupt_task() {
    VL53L0X_Error Status;
    gpio_init(VL53L0X_GPIO_IRQ);

    gpio_set_irq_enabled_with_callback(VL53L0X_GPIO_IRQ, GPIO_IRQ_EDGE_FALL, true, gpioirq_cb);
    Status = VL53L0X_SetDeviceMode(&gVL53L0XDevice, VL53L0X_DEVICEMODE_CONTINUOUS_RANGING); 
    if (Status != VL53L0X_ERROR_NONE) {
        printf("VL52L01 Device mode error\n");
        return;
    }
    Status = VL53L0X_StartMeasurement(&gVL53L0XDevice);
    while(1) {
        if (vl53l0x_irq_data_ready) {
            vl53l0x_irq_data_ready=false;
            Status=VL53L0X_GetRangingMeasurementData(&gVL53L0XDevice, &gRangingData);
            if (Status == VL53L0X_ERROR_NONE && gRangingData.RangeStatus == VL53L0X_ERROR_NONE) { 
                printf("Ranging data:%4d mm\n", gRangingData.RangeMilliMeter);
                
            }
            sleep_ms(10); // 
            VL53L0X_ClearInterruptMask(&gVL53L0XDevice, VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
        }
   }

    VL53L0X_StopMeasurement(&gVL53L0XDevice);
    
}

VL53L0X_Error singleRanging(VL53L0X_Dev_t *pDevice, uint16_t *MeasuredData) {
    VL53L0X_Error Status;
    Status = VL53L0X_SingleRanging(pDevice, MeasuredData);
    /* 
    if (Status == VL53L0X_ERROR_NONE) 
        printf("Measured distance: %d\n",*MeasuredData);
    else 
        printf("measure error\n");
    */
    return Status;
}

VL53L0X_Error continuousRanging(VL53L0X_Dev_t *pDevice, uint16_t *ContinuousData, uint16_t *validCount) {
    uint32_t sum=0;

    uint16_t MeasuredData=0;
    VL53L0X_Error Status;
    sum=0;
    Status = VL53L0X_ContinuousRanging(pDevice, ContinuousData, 16, validCount);
    for (int i = 0; i < *validCount; i++) {
        sum += ContinuousData[i];
    }
    if (*validCount > 0) {
        MeasuredData = sum/(*validCount);
        printf("Average continuous measured distance: %4d,\n" 
                "\tmeasuerd count: %d, valid count: %d\n\n",MeasuredData, 16, *validCount);
    }    else {  
        printf("measure error\n");
    }
    return Status;

}

int main(void)
{
    VL53L0X_Error Status = VL53L0X_ERROR_NONE;
    VL53L0X_Dev_t *pDevice = &gVL53L0XDevice;

    stdio_init_all();
    
    gpio_init(RED_LED_PIN);
    gpio_init(GREEN_LED_PIN);
    gpio_init(BLUE_LED_PIN);

    gpio_set_dir_out_masked(1 << RED_LED_PIN|1 << GREEN_LED_PIN|1 << BLUE_LED_PIN);

    
    pDevice->I2cDevAddr      =  0x29; 
    pDevice->comms_type      =  1;  
    pDevice->comms_speed_khz =  400;

    Status = VL53L0X_dev_i2c_default_initialise(pDevice, VL53L0X_DEFAULT_MODE);
 
    // interrupt ranging test
    //multicore_launch_core1(core1__interrupt_task);
    //while (1);

    uint16_t continuousRingingValue[32];
    uint16_t validCount;
    uint16_t ranging_value=32;
    while(1) { 
        
        //continuousRanging(pDevice, continuousRingingValue, &validCount);
       
        Status = singleRanging(pDevice, &ranging_value);
        if (Status == VL53L0X_ERROR_NONE) {
            gpio_put_masked(1 << RED_LED_PIN|1 << GREEN_LED_PIN|1 << BLUE_LED_PIN, 0);
            if (ranging_value < 200) 
                gpio_put(RED_LED_PIN, true);
            else if (ranging_value < 400)
                    gpio_put(BLUE_LED_PIN, true);
                else 
                    gpio_put(GREEN_LED_PIN, true);
        }
        
       
    }

    return 0;
}

  • CMakeLists.txt(root)


# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(pico_vl53l0x C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(pico_vl53l0x pico_vl53l0x.c )

pico_set_program_name(pico_vl53l0x "pico_vl53l0x")
pico_set_program_version(pico_vl53l0x "0.1")

pico_enable_stdio_uart(pico_vl53l0x 0)
pico_enable_stdio_usb(pico_vl53l0x 1)

# Add the standard library to the build
target_link_libraries(pico_vl53l0x
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(pico_vl53l0x PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
target_link_libraries(pico_vl53l0x 
        hardware_i2c
        pico_multicore
        )
add_subdirectory(vl53l0x_api_rp2040)
target_link_libraries(pico_vl53l0x
  vl53l0x_api_rp2040
      )
pico_add_extra_outputs(pico_vl53l0x)


2023年11月1日 星期三

[Raspberry Pi Pico] Audio Recorder USB device using INMP441 I2S microphone

本文章介紹使用Raspberry Pi Pico的PIO功能,擷取INMP441 I2S microphone製作一台錄音機,將檔案存在SD卡上。連接USB Host時,可透過USB MSC device讀取錄音檔案。


一、測試項目:

  1. 根據inmp441 datasheet擷取24 bits的 32 bit frame, LSB 8bit 為0, 2 channels, 32000 sample rate.
  2. 擷取16 bit 的32 bit frame, LSB 16 bits補0, 2 channels, 32000 sample rate.
  3. 擷取16 bit 的16 bit frame, 2 channels, 44100 sample rate.
  4. 擷取16 bit 的16 bit frame, 1 channels, 44100 sample rate.

二、INMP441 datasheet節錄:


  1. The slave serial-data port’s format is I2S, 24-bit, two's complement.
  2. There must be 64 SCK cycles in each WS stereo frame, or 32 SCK cycles per data-word.
  3. The default data format is I2S (two’s complement), MSB-first. In this format, the MSB of each word is delayed by one SCK cycle from the start of each half-frame

三、程式說明:

使用兩個DMA channel擷取資料到8 KB RAM buffer, 每個DMA channel擷取資料後chan_to到另一個DMA channel,繼續擷取資料,此時將第一個DMA擷取的資料寫到SD上。

根據datasheet的clock時序圖,PIO程式碼如下。

實作後測試發現錄音檔音量非常小,檢視原始資料發現MSB均為00 or FF,
因為資料是2's complement,如下圖所示 00=+0, ff=-0,所以音量非常小。

因此,修正擷取資料程式,忽略MSB 8 bit(保留signed bit)只擷取16 bits,測試結果如「成果影片」所示。
詳細程式碼附於文章末尾。

四、成果影片:


五、程式碼:


有關msc_device_disk與spi_sdmmc詳細說明可參考前篇文章說明。
  • CMakeLists.txt(pico_audio_recorder)
add_library(pico_audio_recorder INTERFACE)
pico_generate_pio_header(pico_audio_recorder ${CMAKE_CURRENT_LIST_DIR}/pico_audio_recorder.pio)
target_sources(pico_audio_recorder INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/pico_audio_recorder.c
)

target_include_directories(pico_audio_recorder INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(pico_audio_recorder INTERFACE
        hardware_dma
        hardware_pio
        hardware_rtc
        pico_stdlib
)

  • pico_audio_recorder.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "string.h"
#include "inttypes.h"
#include "pico_audio_recorder.pio.h"
#include "pico_audio_recorder.h"
#include "tusb.h"
#include "hardware/clocks.h"
#include "hardware/rtc.h"
#include "spi_sdmmc.h"

static int32_t buff1[PIO_DMA_BUFFER_SIZE/4];
static int32_t buff2[PIO_DMA_BUFFER_SIZE/4];
static int dma_read_buff1_channel;
static int dma_read_buff2_channel;
static int32_t *fw_ptr = buff1;
static bool new_read_data=false;
static uint8_t recorder_state=STATE_STOP;

static uint16_t channels = INMP441_CHANNEL_MONO;
static uint16_t bitPerSample = INMP441_BIT_PER_SAMPLE_16;
static uint32_t sampleRate = INMP441_SAMPLE_RATE_44_1K;


void start_stop_button_cb(uint gpio, uint32_t event_mask) {

  if (gpio == BUTTON_PIN) {
    if (event_mask == GPIO_IRQ_EDGE_RISE) { 
        gpio_acknowledge_irq(BUTTON_PIN, GPIO_IRQ_EDGE_RISE);
        switch (get_recorder_state()) {
          case STATE_STOP:
            set_recorder_state(STATE_START_RECORDING);
          break;
          case STATE_RECORDING:
            set_recorder_state(STATE_STOPING);
          break;
          case STATE_START_RECORDING:
          break;
          case STATE_STOPING:
          break;

        }
    }
  
    
  }
}

void pio_irq_read_data() {
     if (pio_interrupt_get(INMP441_pio, 0)) {
        pio_interrupt_clear(INMP441_pio, 0);
        printf("irq 0:\n");
        //dma_channel_start(dma_read_buff1_channel);
     }
     if (pio_interrupt_get(INMP441_pio, 1)) {
        pio_interrupt_clear(INMP441_pio, 1);
        printf("irq:1\n");
     }
}


void dma_handler() {
   if (dma_channel_get_irq1_status(dma_read_buff1_channel)) {
      dma_channel_acknowledge_irq1(dma_read_buff1_channel);
      dma_channel_set_write_addr(dma_read_buff1_channel, buff1, false);
      fw_ptr = buff1;
      new_read_data=true;
   }
   if (dma_channel_get_irq1_status(dma_read_buff2_channel)) {
      dma_channel_acknowledge_irq1(dma_read_buff2_channel);
      dma_channel_set_write_addr(dma_read_buff2_channel, buff2, false);
      fw_ptr = buff2;
      new_read_data=true;
   }
}

void set_pin_LED(bool green) {
   if (green) {
      gpio_put(GREEN_PIN, true);
      gpio_put(RED_PIN, false);
   } else {
      gpio_put(GREEN_PIN, false);
      gpio_put(RED_PIN, true);
   }
}

/*!
* \brief sdio memory card pio initialize
* \param pio: pio number
* \param sm state machine
* \param cmd_pin command pin
* \param clk_pin CLK pin
* \param data_pin_base data 0~4 pins(D0~D3)
* \param clk_int Integer part of the divisor
* \param clk_frac – Fractional part in 1/256ths
*/
void inmp441_pio_init(PIO pio,  uint sm, uint sd_pin, uint sideset_pin) { // sideset_pin ws_clk
  
    pio_gpio_init(pio, sd_pin);
    pio_gpio_init(pio, sideset_pin);
    pio_gpio_init(pio, sideset_pin+1);
    gpio_pull_down(sd_pin);
    //gpio_pull_up(sideset_pin);
    //gpio_pull_up(sideset_pin+1);
    
    //== INMP441 SM ==
    uint offset = pio_add_program(pio, &inmp441_program);
    pio_sm_config c = inmp441_program_get_default_config(offset);
    pio_sm_set_consecutive_pindirs(pio, sm, sd_pin, 1, false);
    pio_sm_set_consecutive_pindirs(pio, sm, sideset_pin, 2, true); // WS, SCK
    sm_config_set_in_pins(&c, sd_pin);
    sm_config_set_set_pins(&c, sideset_pin,2);
    sm_config_set_sideset_pins(&c, sideset_pin);
    
    sm_config_set_in_shift(&c, false, true, bitPerSample);// 16 or 32
   
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
    float div = clock_get_hz(clk_sys)/(sampleRate*64*2); // 64 clocks, 2 PIO clocks
    sm_config_set_clkdiv(&c, div);
 
    pio_sm_init(pio, sm, offset, &c);

      // initial state machine but not run
    pio_sm_set_enabled(pio, sm, false);

    //** for test only
    uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    //pio_set_irq0_source_enabled(pio, pis_interrupt1, true);
    irq_add_shared_handler(pio_irq, pio_irq_read_data, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq, true);
   
    uint pio_base = (pio==pio0)?PIO0_BASE:PIO1_BASE;

    //==== READ DMA ===
    dma_read_buff1_channel = dma_claim_unused_channel(true);
    dma_read_buff2_channel = dma_claim_unused_channel(true);

    dma_channel_config dc1 = dma_channel_get_default_config(dma_read_buff1_channel);
    channel_config_set_write_increment(&dc1, true);
    channel_config_set_read_increment(&dc1, false);
    channel_config_set_dreq(&dc1, pio_get_dreq(pio, sm, false));
    channel_config_set_chain_to(&dc1, dma_read_buff2_channel);  
    //  for 32 bit frame
    if (bitPerSample == INMP441_BIT_PER_SAMPLE_32) {
         channel_config_set_transfer_data_size(&dc1, DMA_SIZE_32); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff1_channel,
                  &dc1, buff1, (void*) (pio_base+PIO_RXF0_OFFSET),  // 
                  PIO_DMA_BUFFER_SIZE>> DMA_SIZE_32, false); //DMA_SIZE_8 or 16 or 32
    }
  // for 16 bit frame
  if (bitPerSample == INMP441_BIT_PER_SAMPLE_16) {
         channel_config_set_transfer_data_size(&dc1, DMA_SIZE_16); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff1_channel,
               &dc1, buff1, (void*) (pio_base+PIO_RXF0_OFFSET),  // 
               PIO_DMA_BUFFER_SIZE>> DMA_SIZE_16, false); //DMA_SIZE_8 or 16 or 32
  }

   dma_channel_config dc2 = dma_channel_get_default_config(dma_read_buff2_channel);
    channel_config_set_write_increment(&dc2, true);
    channel_config_set_read_increment(&dc2, false);
    //channel_config_set_bswap(&dc2, true);
    channel_config_set_dreq(&dc2, pio_get_dreq(pio, sm, false));
    channel_config_set_chain_to(&dc2, dma_read_buff1_channel); 

   // for 32 bit frame
   if (bitPerSample == INMP441_BIT_PER_SAMPLE_32) {
         channel_config_set_transfer_data_size(&dc2, DMA_SIZE_32); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff2_channel,
                  &dc2, buff2, (void*) (pio_base+PIO_RXF0_OFFSET),  // SDIO_DATA_READ_SM = 1
                  PIO_DMA_BUFFER_SIZE>> DMA_SIZE_32, false); //DMA_SIZE_8 or 16 or 32
   }
   // for 16 bit frame
   if (bitPerSample == INMP441_BIT_PER_SAMPLE_16) {
         channel_config_set_transfer_data_size(&dc2, DMA_SIZE_16); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff2_channel,
                  &dc2, buff2, (void*) (pio_base+PIO_RXF0_OFFSET),  // SDIO_DATA_READ_SM = 1
                  PIO_DMA_BUFFER_SIZE>> DMA_SIZE_16, false); //DMA_SIZE_8 or 16 or 32
   }

   dma_channel_set_irq1_enabled(dma_read_buff1_channel, true);
   dma_channel_set_irq1_enabled(dma_read_buff2_channel, true);

    // Configure the processor to run dma_handler() when DMA IRQ 0 is asserted
    //irq_set_exclusive_handler(DMA_IRQ_1, dma_handler);
    irq_add_shared_handler(DMA_IRQ_1, dma_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(DMA_IRQ_1, true);
   
   gpio_init(BUTTON_PIN);
   gpio_set_dir(BUTTON_PIN, false);
   gpio_pull_down(BUTTON_PIN);
   gpio_init(RED_PIN);
   gpio_set_dir(RED_PIN, true);
   gpio_init(GREEN_PIN);
   gpio_set_dir(GREEN_PIN, true);
   recorder_state = STATE_STOP;
   set_pin_LED(true);

   gpio_set_irq_enabled_with_callback(BUTTON_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, start_stop_button_cb);
}

bool write_file_wave_header(FIL *fil) {
   UINT bw;
	WAVE_HEADER wave_header;
	wave_header.riff[0] = 'R';wave_header.riff[1] = 'I';
	wave_header.riff[2] = 'F';wave_header.riff[3] = 'F';
	wave_header.size = (uint32_t)0;
	wave_header.wave[0] = 'W';wave_header.wave[1] = 'A';
	wave_header.wave[2] = 'V';wave_header.wave[3] = 'E';
	wave_header.fmt[0] = 'f';wave_header.fmt[1] = 'm';
	wave_header.fmt[2] = 't';wave_header.fmt[3] = ' ';
	wave_header.fmt_size = 16;
	wave_header.format = 1; // PCM
	wave_header.channels = channels; // channels
	wave_header.sampleRate=sampleRate;  // sample rate
	wave_header.rbc = sampleRate*bitPerSample*channels/8;
	wave_header.bc =  bitPerSample*channels/8;
	wave_header.bitsPerSample = bitPerSample; //bitsPerSample
	wave_header.data[0] = 'd'; wave_header.data[1] = 'a';
	wave_header.data[2] = 't'; wave_header.data[3] = 'a';
	wave_header.data_size = 0;
	if (f_write(fil, (uint8_t*)&wave_header, sizeof(wave_header), &bw)!= FR_OK) return false;
   return true;
}
bool modify_file_wave_header(FIL *fil, uint32_t data_length) {
   uint bw;
	uint32_t total_len = data_length+36;
	f_lseek(fil, 4);
	f_write(fil, (uint8_t*)&total_len, 4, &bw);
	f_lseek(fil, 40);
	f_write(fil, (uint8_t*)&data_length, 4, &bw);

   return true;
}

void inmp441_starting_recording_to_file_wav() { 
   uint32_t wav;
   uint bw;
   FIL fil;
   uint8_t filename[100];
   datetime_t t;
   FRESULT res;
   uint32_t data_length;
   FATFS fs;
   
   rtc_get_datetime(&t);
   tud_disconnect();
   sleep_ms(1);
   if (f_mount(&fs, SDMMC_PATH,1) != FR_OK) {
      tud_connect();
      sleep_ms(1);
      return;
   }
   
   sprintf(filename, "%s/V%02d%02d%02d-%02d%02d%02d.wav", SDMMC_PATH,t.year%100,t.month,t.day,t.hour,t.min,t.sec);
   if (f_open(&fil, filename, FA_CREATE_ALWAYS|FA_WRITE) != FR_OK) {
      printf("open file error:%s\n", filename);
      tud_connect();
      recorder_state = STATE_STOP;
      set_pin_LED(true);
      return;
   }

   if (!write_file_wave_header(&fil)) {
      tud_connect();
      recorder_state = STATE_STOP;
      set_pin_LED(true);
      return;
   }

   recorder_state = STATE_RECORDING;
   set_pin_LED(false);
   
   pio_sm_exec(INMP441_pio, INMP441_SM, pio_encode_mov(pio_isr, pio_null)); // enpty ISR
   pio_sm_clear_fifos(INMP441_pio, INMP441_SM);
   pio_sm_exec(INMP441_pio, INMP441_SM, pio_encode_jmp(inmp441_offset_inmp441_start));
   //dma_channel_start(dma_read_buff1_channel);
   dma_channel_set_write_addr(dma_read_buff2_channel, buff2, false);
   dma_channel_set_write_addr(dma_read_buff1_channel, buff1, true);
   pio_sm_set_enabled(INMP441_pio, INMP441_SM, true);

   new_read_data=false;
   
   sleep_ms(300);
   data_length=0;
   while (recorder_state == STATE_RECORDING) { 
      if (new_read_data) {
         new_read_data=false;
         f_write(&fil, (uint8_t*)fw_ptr, PIO_DMA_BUFFER_SIZE, &bw);
         data_length += PIO_DMA_BUFFER_SIZE;
      }
        
   } 
   modify_file_wave_header(&fil, data_length);
   f_close(&fil);
   f_unmount(SDMMC_PATH);

   pio_sm_set_enabled(INMP441_pio, INMP441_SM, false);
      
   dma_channel_abort(dma_read_buff1_channel);
   dma_channel_abort(dma_read_buff2_channel);
   
   
   recorder_state = STATE_STOP;
   set_pin_LED(true);
printf("Stop recording\n");
   tud_connect();
}

uint8_t get_recorder_state() {
   return recorder_state;
}
void    set_recorder_state(uint8_t state) {
   recorder_state = state;
}
  • pico_audio_recorder.h

#ifndef __PICO_AUDIO_RECORDER_
#define __PICO_AUDIO_RECORDER_
#include "hardware/pio.h"
#include "ff.h"

#define INMP441_pio         	pio0
#define INMP441_SM          	0

#define INMP441_SCK          	18
#define INMP441_SD           	20

#define INMP441_CHANNEL_STEREO	2
#define INMP441_CHANNEL_MONO	1
#define INMP441_SAMPLE_RATE_32K  	32000
#define INMP441_SAMPLE_RATE_44_1K  	44100

#define INMP441_BIT_PER_SAMPLE_32	32
#define INMP441_BIT_PER_SAMPLE_16	16

#define BUTTON_PIN      		21
#define RED_PIN					16
#define GREEN_PIN				17

#define PIO_DMA_BUFFER_SIZE 8192

enum RECORDER_STATE{
    STATE_STOP=0,
	STATE_STOPING,
    STATE_START_RECORDING,
    STATE_RECORDING,
};

typedef struct _WaveHeader{
	char riff[4];
	uint32_t size;
	char wave[4];
	char fmt[4];
	uint32_t fmt_size;
	uint16_t format; //1:PCM
	uint16_t channels; // channels
	uint32_t sampleRate;  // sample rate
	uint32_t rbc;//sampleRate*bitsPerSample*channels/8
	uint16_t bc; //bitsPerSample*channels/8
	uint16_t bitsPerSample; //bitsPerSample
	char data[4];
	uint32_t data_size;
} WAVE_HEADER;

void inmp441_pio_init(PIO pio,  uint sm, uint sd_pin, uint sideset_pin);
void inmp441_starting_recording_to_file_wav();
uint8_t get_recorder_state();
void    set_recorder_state(uint8_t state);

#endif

  • pico_audio_recorder.pio

/*
;=======  (stereo) get 16 bit in 16 bit frame =======
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:
.wrap_target
; left channel
    set y, 6                    side 0b00  ; ignore 1+8 bit
    nop                         side 0b01  ; one clock delay
    nop                         side 0b00
    in pins, 1                  side 0b01  ; MSB signed bit
left_idle:          
    nop                         side 0b00
    jmp y--, left_idle          side 0b01  ; one clock delay+MSB signed bit+7 ignore bit=9 bits

    set x, 14                   side 0b00 ; other 15 bits
left_channel_loop:
    in  pins, 1                 side 0b01
    jmp x--, left_channel_loop  side 0b00  ; 9+15 clocks

    set y, 7                    side 0b01 ; last 8 clock
left_idle_1:          
    nop                         side 0b00
    jmp y--, left_idle_1        side 0b01 ; cycle:9+15+8=32


; right channel
    set y, 6                    side 0b10  ; ignore 1+8 bit
    nop                         side 0b11  ; one clock delay
    nop                         side 0b10
    in pins, 1                  side 0b11  ; MSB signed bit
right_idle:          
    nop                         side 0b10
    jmp y--, right_idle          side 0b11  ; one clock delay+MSB signed bit+7 ignore bit=9 bits

    set x, 14                   side 0b10
right_channel_loop:
    in  pins, 1                 side 0b11
    jmp x--, right_channel_loop  side 0b10  ; 9+15 clocks

    set y, 7                    side 0b11 
right_idle_1:          
    nop                         side 0b10
    jmp y--, right_idle_1        side 0b11 ; cycle:9+15+8

.wrap
;=======  (stereo) get 16 bit in 16 bit frame =======
*/




;=======  (mono, left channel) get 16 bit in 16 bit frame =======
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:
.wrap_target
; left channel
    set y, 6                    side 0b00  ; ignore 1+8 bit
    nop                         side 0b01  ; one clock delay
    nop                         side 0b00
    in pins, 1                  side 0b01  ; MSB signed bit
left_idle:          
    nop                         side 0b00
    jmp y--, left_idle          side 0b01  ; one clock delay+MSB signed bit+7 ignore bit=9 bits

    set x, 14                   side 0b00
left_channel_loop:
    in  pins, 1                 side 0b01
    jmp x--, left_channel_loop  side 0b00  ; 9+15 clocks

    set y, 7                    side 0b01 
left_idle_1:          
    nop                         side 0b00
    jmp y--, left_idle_1        side 0b01 ; cycle:9+15+8

;right channel 
    set y, 30                   side 0b10
right_idle_loop:
    nop                         side 0b11
    jmp y--, right_idle_loop    side 0b10
    nop                         side 0b11
.wrap
;=======  (mono, left channel) =======


/*
;======= shift out 7 bit and get 16 bit in 32 bit frame =======
.define IGNORE_MSB_BITS  (6+1)   ; one bit delay
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:

.wrap_target
; left channel
    set y, (IGNORE_MSB_BITS-2)  side 0b00
left_idle:          
    nop                         side 0b01
    jmp y--, left_idle          side 0b00
    nop                         side 0b01

    set x, 15                   side 0b00 ; ignore msb 8 bits and capture the other 16 bits
left_channel_loop:
    in  pins, 1                 side 0b01
    jmp x--, left_channel_loop  side 0b00
    nop                         side 0b01

    set y, (13-IGNORE_MSB_BITS) side 0b00
left_idle_1:          
    nop                         side 0b01
    jmp y--, left_idle_1          side 0b00
    in NULL, 16                 side 0b01  


; right channel
    set y, (SHIFT_OUT_BITS-2)  side 0b10
right_idle:          
    nop                         side 0b11
    jmp y--, right_idle          side 0b10
    nop                         side 0b11

    set x, 15                   side 0b10
right_channel_loop:
    in  pins, 1                 side 0b11
    jmp x--, right_channel_loop  side 0b10
    nop                         side 0b11

    set y, (13-SHIFT_OUT_BITS) side 0b10
right_idle_1:          
    nop                         side 0b11
    jmp y--, right_idle_1          side 0b10
    in NULL, 16                 side 0b11  
.wrap
;======= shift out 7 bit and get 16 bit in 32 bit frame =======
*/


/*
;====== (STEREO) INMP441 I2S 32 bit per sample =======================
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:
.wrap_target
; left channel
set x, 22                       side 0b00
nop                             side 0b01 ;delay one SCK clock
nop                             side 0b00 
left_channel_loop:
    in  pins, 1                 side 0b01 ; ; 24 bits 
    jmp x--, left_channel_loop  side 0b00
    in pins, 1                  side 0b01

    set y, 5                    side 0b00 ; 7 SCK clocks
left_idle:          
    nop                         side 0b01
    jmp y--, left_idle          side 0b00
    in NULL, 8                  side 0b01 

; right channel
set x, 22                       side 0b10
nop                             side 0b11
nop                             side 0b10 
right_channel_loop:
    in  pins, 1                 side 0b11
    jmp x--, right_channel_loop side 0b10
    in pins, 1                  side 0b11

    set y, 5                    side 0b10
right_idle:          
    nop                         side 0b11
    jmp y--, right_idle         side 0b10
    in NULL, 8                  side 0b11  
.wrap

*/
  • glue.c

#include "stdio.h"
#include "pico/stdlib.h"
#include "stdlib.h"
#include "ff.h"
#include "diskio.h"
#include "pico_storage.h"
#include "msc_storage_driver.h"
#include "spi_sdmmc.h"
#include "hardware/rtc.h"
#include "inttypes.h"
#include "hardware/gpio.h"


#define SDMMC_DRV_0     0
sdmmc_data_t *pSDMMC=NULL;

//==================//
DSTATUS disk_initialize (BYTE drv){
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            if (pSDMMC == NULL) {
                pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
                pSDMMC->csPin = SDMMC_PIN_CS;
                pSDMMC->spiPort = SDMMC_SPI_PORT;
                pSDMMC->spiInit=false;
                pSDMMC->sectSize=512;
#ifdef __SPI_SDMMC_DMA
                pSDMMC->dmaInit=false;
#endif
             stat = sdmmc_disk_initialize(pSDMMC);
            return stat;
        } else {
            return RES_OK;
        }
           
        break;
        
    }
    return STA_NOINIT;
 }
/*-----------------------------------------------------------------------*/
/* Get disk status                                                       */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (BYTE drv) {
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat=  sdmmc_disk_status(pSDMMC); /* Return disk status */
            return stat;
        break;
        
    }
    return RES_PARERR;
	
}

/*-----------------------------------------------------------------------*/
/* Read sector(s)                                                        */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE drv,		/* Physical drive number (0) */
	BYTE *buff,		/* Pointer to the data buffer to store read data */
	LBA_t sector,	/* Start sector number (LBA) */
	UINT count		/* Number of sectors to read (1..128) */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_read(buff, sector, count, pSDMMC);
            return stat;
        break;
        
    }
	return RES_PARERR;
}

/*-----------------------------------------------------------------------*/
/* Write sector(s)                                                       */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
	BYTE drv,			/* Physical drive number (0) */
	const BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count			/* Number of sectors to write (1..128) */
)
{
    DSTATUS stat = STA_NODISK;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_write(buff, sector, count, pSDMMC);
            return stat;
        break;
        
    }
	return RES_PARERR;

}
#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous drive controls other than data read/write               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE drv,		/* Physical drive number (0) */
	BYTE cmd,		/* Control command code */
	void *buff		/* Pointer to the conrtol data */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_ioctl(cmd, buff, pSDMMC);
            return stat;
        break;
        
    }
	return RES_PARERR;
}

DWORD get_fattime(void) {
    datetime_t t ;
    bool rc = rtc_get_datetime(&t);
    if (!rc) return 0;

    DWORD fattime = 0;
    // bit31:25
    // Year origin from the 1980 (0..127, e.g. 37 for 2017)
    uint32_t yr = t.year - 1980;
    fattime |= (yr & 0b01111111) << 25;
    // bit24:21
    // Month (1..12)
    uint32_t mo = t.month;
    fattime |= ( mo & 0b00001111) << 21;
    // bit20:16
    // Day of the month (1..31)
    uint32_t da = t.day;
    fattime |= (da & 0b00011111) << 16;
    // bit15:11
    // Hour (0..23)
    uint32_t hr = t.hour;
    fattime |= (hr & 0b00011111) << 11;
    // bit10:5
    // Minute (0..59)
    uint32_t mi = t.min;
    fattime |= ( mi&0b00111111) << 5;
    // bit4:0
    // Second / 2 (0..29, e.g. 25 for 50)
    uint32_t sd = t.sec / 2;
    fattime |= (sd&0b00011111);
    return fattime;
}

void led_blinking(void)
{
    static absolute_time_t  t1;
    static bool state=false;
        
    // Blink every interval ms
    if ( absolute_time_diff_us(t1, get_absolute_time()) < 100000) return; // not enough time
    t1 = get_absolute_time();
    gpio_put(LED_BLINKING_PIN, state);
    state = !state;
}

void led_blinking_off(void) {
    gpio_put(LED_BLINKING_PIN, false);
}

  • spi_sdmmc.c

/*
This library is derived from ChaN's FatFs - Generic FAT Filesystem Module.
*/
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "spi_sdmmc.h"
#include "msc_storage_driver.h"


#define SDMMC_CD 0 // card detect
#define SDMMC_WP 0 // write protected

static uint8_t dummy_block[SDMMC_SECT_SIZE];

void sdmmc_spi_cs_high(sdmmc_data_t *sdmmc);
void sdmmc_spi_cs_low(sdmmc_data_t *sdmmc);
static int sdmmc_wait_ready(uint timeout, sdmmc_data_t *sdmmc);
static void sdmmc_init_spi(sdmmc_data_t *sdmmc);

static void sdmmc_deselect(sdmmc_data_t *sdmmc)
{
	uint8_t src = 0xFF;
	sdmmc_spi_cs_high(sdmmc);
	spi_write_blocking(sdmmc->spiPort, &src, 1);
}

/*-----------------------------------------------------------------------*/
/* Select card and wait for ready                                        */
/*-----------------------------------------------------------------------*/
static int sdmmc_select(sdmmc_data_t *sdmmc) /* 1:OK, 0:Timeout */
{
	uint8_t src = 0xFF;
	sdmmc_spi_cs_low(sdmmc);
	spi_write_blocking(sdmmc->spiPort, &src, 1);
	if (sdmmc_wait_ready(500, sdmmc))
		return 1; /* Wait for card ready */
	sdmmc_deselect(sdmmc);
	return 0; /* Timeout */
}

uint64_t sdmmc_get_sector_count(sdmmc_data_t *sdmmc) {
	uint8_t n, csd[16];
	uint32_t st, ed, csize;

	uint64_t sectorCounter;

	uint8_t src = 0xFF;
	uint8_t sc = sdmmc_send_cmd(CMD9, 0, sdmmc);
	int rd = sdmmc_read_datablock(csd, 16, sdmmc);
	
	if ((sc == 0) && rd)
	{
		if ((csd[0] >> 6) == 1)
		{ /* SDC CSD ver 2 */
			csize = csd[9] + ((uint16_t)csd[8] << 8) + ((uint32_t)(csd[7] & 63) << 16) + 1;
			sectorCounter = csize << 10;
		}
		else
		{ /* SDC CSD ver 1 or MMC */
			n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
			csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
			sectorCounter = csize << (n - 9);
		}
	
	} else {
		sectorCounter=0;
	}
	sdmmc_deselect(sdmmc);
	return sectorCounter;
}

uint32_t sdmmc_get_block_count(sdmmc_data_t *sdmmc) {
	uint8_t n, csd[16];
	uint32_t st, ed, csize;

	uint32_t sectorCounter=0;

	uint8_t src = 0xFF;

	if (sdmmc->cardType & CT_SDC2)
	{ /* SDC ver 2+ */
		if (sdmmc_send_cmd(ACMD13, 0, sdmmc) == 0)
		{ /* Read SD status */
			spi_write_blocking(sdmmc->spiPort, &src, 1);
			if (sdmmc_read_datablock(csd, 16, sdmmc))
			{ /* Read partial block */
				for (n = 64 - 16; n; n--)
					spi_write_blocking(sdmmc->spiPort, &src, 1); // xchg_spi(0xFF);	/* Purge trailing data */
				sectorCounter = 16UL << (csd[10] >> 4);
			}
		}
	}
	else
	{ /* SDC ver 1 or MMC */
		if ((sdmmc_send_cmd(CMD9, 0, sdmmc) == 0) && sdmmc_read_datablock(csd, 16, sdmmc))
		{ /* Read CSD */
			if (sdmmc->cardType & CT_SDC1)
			{ /* SDC ver 1.XX */
				sectorCounter = (((csd[10] & 63) << 1) + ((uint16_t)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
			}
			else
			{ /* MMC */
				sectorCounter = ((uint16_t)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
			}

		}
	}
	sdmmc_deselect(sdmmc);
	return sectorCounter;
}

uint8_t  msc_sdmmc_read_sector(uint32_t sector, uint8_t* buff, uint32_t len, sdmmc_data_t *sdmmc) {
	uint8_t ret=0;
	uint count;
	count = (len % sdmmc->sectSize)  ? ((len / sdmmc->sectSize) + 1) : (len / sdmmc->sectSize);
	
	if (!count)
		return ret; /* Check parameter */

	if (!(sdmmc->cardType & CT_BLOCK))
		sector *= sdmmc->sectSize; /* LBA ot BA conversion (byte addressing cards) */
	if (count == 1)
	{												  /* Single sector read */
		if ((sdmmc_send_cmd(CMD17, sector, sdmmc) == 0) /* READ_SINGLE_BLOCK */
			&& sdmmc_read_datablock(buff, sdmmc->sectSize, sdmmc))
		{
			ret = 1;
		}
led_blinking_task(); //// LED blinking
	}
	else
	{ /* Multiple sector read */
		if (sdmmc_send_cmd(CMD18, sector, sdmmc) == 0)
		{ /* READ_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_read_datablock(buff, sdmmc->sectSize, sdmmc))
					break;
				buff += sdmmc->sectSize;
led_blinking_task(); //// LED blinking
			} while (--count);
			
			sdmmc_send_cmd(CMD12, 0, sdmmc); /* STOP_TRANSMISSION */
			ret = 1;
		}
	}
led_blinking_task_off();  //// LED blinking off
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd()

	return ret;
}

uint8_t msc_sdmmc_write_sector(uint32_t sector, uint8_t *buff, uint32_t len, sdmmc_data_t *sdmmc) {

	uint8_t ret=0;
	uint count;
	count = (len % sdmmc->sectSize)  ? ((len / sdmmc->sectSize)+1) : (len / sdmmc->sectSize);

	if (!count)
		return ret; /* Check parameter */
	//if (sdmmc->Stat & STA_NOINIT)
	//	return RES_NOTRDY; /* Check drive status */
	//if (sdmmc->Stat & STA_PROTECT)
	//	return RES_WRPRT; /* Check write protect */

	if (!(sdmmc->cardType & CT_BLOCK))
		sector *= sdmmc->sectSize; /* LBA ==> BA conversion (byte addressing cards) */

	if (count == 1)
	{												  /* Single sector write */
		if ((sdmmc_send_cmd(CMD24, sector, sdmmc) == 0) /* WRITE_BLOCK */
			&& sdmmc_write_datablock(buff, 0xFE, sdmmc))
		{
			ret = 1;
		}
led_blinking_task();  //// LED_blinking
	}
	else
	{ /* Multiple sector write */
		if (sdmmc->cardType & CT_SDC)
			sdmmc_send_cmd(ACMD23, count, sdmmc); /* Predefine number of sectors */
		if (sdmmc_send_cmd(CMD25, sector, sdmmc) == 0)
		{ /* WRITE_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_write_datablock(buff, 0xFC, sdmmc))
					break;
				buff += sdmmc->sectSize;

led_blinking_task();  //// LED_blinking
			} while (--count);
			  // LED blinking off
			if (!sdmmc_write_datablock(0, 0xFD, sdmmc))
				count = 1; /* STOP_TRAN token */
			ret =1;
		}
	}
led_blinking_task_off();
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd

}

/* sdmmc spi port initialize*/
void sdmmc_spi_port_init(sdmmc_data_t *sdmmc)
{
	sdmmc->spiPort = SDMMC_SPI_PORT;
	sdmmc->csPin = SDMMC_PIN_CS;
	spi_init(sdmmc->spiPort, SPI_BAUDRATE_LOW);
	gpio_set_function(SDMMC_PIN_MISO, GPIO_FUNC_SPI);
	gpio_set_function(sdmmc->csPin, GPIO_FUNC_SIO);
	gpio_set_function(SDMMC_PIN_SCK, GPIO_FUNC_SPI);
	gpio_set_function(SDMMC_PIN_MOSI, GPIO_FUNC_SPI);
	gpio_set_dir(sdmmc->csPin, GPIO_OUT);
	gpio_put(sdmmc->csPin, 1); // deselect

	sdmmc->spiInit = true; // alreadily initialized
}

/* config spi dma*/
#ifdef __SPI_SDMMC_DMA
void config_spi_dma(sdmmc_data_t *sdmmc)
{
	sdmmc->read_dma_ch = dma_claim_unused_channel(true);
	sdmmc->write_dma_ch = dma_claim_unused_channel(true);
	sdmmc->dma_rc = dma_channel_get_default_config(sdmmc->read_dma_ch);
	sdmmc->dma_wc = dma_channel_get_default_config(sdmmc->write_dma_ch);
	channel_config_set_transfer_data_size(&(sdmmc->dma_rc), DMA_SIZE_8);
	channel_config_set_transfer_data_size(&(sdmmc->dma_wc), DMA_SIZE_8);
	channel_config_set_read_increment(&(sdmmc->dma_rc), false);
	channel_config_set_write_increment(&(sdmmc->dma_rc), true);
	channel_config_set_read_increment(&(sdmmc->dma_wc), true);
	channel_config_set_write_increment(&(sdmmc->dma_wc), false);
	channel_config_set_dreq(&(sdmmc->dma_rc), spi_get_dreq(sdmmc->spiPort, false));
	channel_config_set_dreq(&(sdmmc->dma_wc), spi_get_dreq(sdmmc->spiPort, true));

	for (int i = 0; i < SDMMC_SECT_SIZE; i++)
		dummy_block[i] = 0xFF;

	dma_channel_configure(sdmmc->read_dma_ch,
						  &(sdmmc->dma_rc),
						  NULL,
						  &spi_get_hw(sdmmc->spiPort)->dr,
						  sdmmc->sectSize, false);
	dma_channel_configure(sdmmc->write_dma_ch,
						  &(sdmmc->dma_wc),
						  &spi_get_hw(sdmmc->spiPort)->dr,
						  NULL,
						  sdmmc->sectSize, false);
	sdmmc->dmaInit = true;
}
#endif

/* set spi cs low (select)*/
void sdmmc_spi_cs_low(sdmmc_data_t *sdmmc)
{
	gpio_put(sdmmc->csPin, 0);
}
/* set spi cs high (deselect)*/
void sdmmc_spi_cs_high(sdmmc_data_t *sdmmc)
{
	gpio_put(sdmmc->csPin, 1);
}
/* Initialize SDMMC SPI interface */
static void sdmmc_init_spi(sdmmc_data_t *sdmmc)
{
	sdmmc_spi_port_init(sdmmc); // if not initialized, init it
#ifdef __SPI_SDMMC_DMA
	if (!sdmmc->dmaInit)
		config_spi_dma(sdmmc);
#endif

	sleep_ms(10);
}

/* Receive a sector data (512 uint8_ts) */
static void sdmmc_read_spi_dma(
	uint8_t *buff, /* Pointer to data buffer */
	uint btr,	/* Number of uint8_ts to receive (even number) */
	sdmmc_data_t *sdmmc)
{
#ifdef __SPI_SDMMC_DMA
	dma_channel_set_read_addr(sdmmc->write_dma_ch, dummy_block, false);
	dma_channel_set_trans_count(sdmmc->write_dma_ch, btr, false);

	dma_channel_set_write_addr(sdmmc->read_dma_ch, buff, false);
	dma_channel_set_trans_count(sdmmc->read_dma_ch, btr, false);

	dma_start_channel_mask((1u << (sdmmc->read_dma_ch)) | (1u << (sdmmc->write_dma_ch)));
	dma_channel_wait_for_finish_blocking(sdmmc->read_dma_ch);
#else
	spi_read_blocking(sdmmc->spiPort, 0xFF, buff, btr);
#endif
}


/* Send a sector data (512 uint8_ts) */
static void sdmmc_write_spi_dma(
	const uint8_t *buff, /* Pointer to the data */
	uint btx,		  /* Number of uint8_ts to send (even number) */
	sdmmc_data_t *sdmmc)
{
#ifdef __SPI_SDMMC_DMA
	dma_channel_set_read_addr(sdmmc->write_dma_ch, buff, false);
	dma_channel_set_trans_count(sdmmc->write_dma_ch, btx, false);
	dma_channel_start(sdmmc->write_dma_ch);
	dma_channel_wait_for_finish_blocking(sdmmc->write_dma_ch);
#else
	spi_write_blocking(sdmmc->spiPort, buff, btx);
#endif
}


/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                   */
/*-----------------------------------------------------------------------*/
static int sdmmc_wait_ready(uint timeout, sdmmc_data_t *sdmmc)
{
	uint8_t dst;
	absolute_time_t timeout_time = make_timeout_time_ms(timeout);
	do
	{
		spi_read_blocking(sdmmc->spiPort, 0xFF, &dst, 1);
	} while (dst != 0xFF && 0 < absolute_time_diff_us(get_absolute_time(), timeout_time)); /* Wait for card goes ready or timeout */

	return (dst == 0xFF) ? 1 : 0;
}

/*-----------------------------------------------------------------------*/
/* Deselect card and release SPI                                         */
/*-----------------------------------------------------------------------*/


/*-----------------------------------------------------------------------*/
/* Receive a data packet from the MMC                                    */
/*-----------------------------------------------------------------------*/
// static
int sdmmc_read_datablock(			 /* 1:OK, 0:Error */
						 uint8_t *buff, /* Data buffer */
						 uint btr,	 /* Data block length (uint8_t) */
						 sdmmc_data_t *sdmmc)
{
	uint8_t token;
	absolute_time_t timeout_time = make_timeout_time_ms(200);
	do
	{ /* Wait for DataStart token in timeout of 200ms */
		spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
		
	} while ((token == 0xFF) && 0 < absolute_time_diff_us(get_absolute_time(), timeout_time));
	if (token != 0xFE)
		return 0; /* Function fails if invalid DataStart token or timeout */

	sdmmc_read_spi_dma(buff, btr, sdmmc);
	// Discard CRC
	spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
	spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
	return 1; // Function succeeded
}

/*-----------------------------------------------------------------------*/
/* Send a data packet to the MMC                                         */
/*-----------------------------------------------------------------------*/
//#if FF_FS_READONLY == 0
// static
int sdmmc_write_datablock(					/* 1:OK, 0:Failed */
						  const uint8_t *buff, /* Ponter to 512 uint8_t data to be sent */
						  uint8_t token,		/* Token */
						  sdmmc_data_t *sdmmc)
{
	uint8_t resp;
	if (!sdmmc_wait_ready(500, sdmmc))
		return 0; /* Wait for card ready */
	// Send token : 0xFE--single block, 0xFC -- multiple block write start, 0xFD -- StopTrans
	spi_write_blocking(sdmmc->spiPort, &token, 1);
	if (token != 0xFD)
	{										   /* Send data if token is other than StopTran */
		sdmmc_write_spi_dma(buff, sdmmc->sectSize, sdmmc); /* Data */

		token = 0xFF;
		spi_write_blocking(sdmmc->spiPort, &token, 1); // Dummy CRC
		spi_write_blocking(sdmmc->spiPort, &token, 1);

		spi_read_blocking(sdmmc->spiPort, 0xFF, &resp, 1);
		// receive response token: 0x05 -- accepted, 0x0B -- CRC error, 0x0C -- Write Error
		if ((resp & 0x1F) != 0x05)
			return 0; /* Function fails if the data packet was not accepted */
	}
	return 1;
}
//#endif

/*-----------------------------------------------------------------------*/
/* Send a command packet to the MMC                                      */
/*-----------------------------------------------------------------------*/
//static
uint8_t sdmmc_send_cmd(			  /* Return value: R1 resp (bit7==1:Failed to send) */
						   uint8_t cmd,  /* Command index */
						   uint32_t arg, /* Argument */
						   sdmmc_data_t *sdmmc)
{
	uint8_t n, res;
	uint8_t tcmd[5];

	if (cmd & 0x80)
	{ /* Send a CMD55 prior to ACMD */
		cmd &= 0x7F;
		res = sdmmc_send_cmd(CMD55, 0, sdmmc);
		if (res > 1)
			return res;
	}

	/* Select the card and wait for ready except to stop multiple block read */
	if (cmd != CMD12)
	{
		sdmmc_deselect(sdmmc);
		if (!sdmmc_select(sdmmc))
			return 0xFF;
	}

	/* Send command packet */
	tcmd[0] = 0x40 | cmd;		 // 0 1 cmd-index(6) --> 01xxxxxx(b)
	tcmd[1] = (uint8_t)(arg >> 24); // 32 bits argument
	tcmd[2] = (uint8_t)(arg >> 16);
	tcmd[3] = (uint8_t)(arg >> 8);
	tcmd[4] = (uint8_t)arg;
	spi_write_blocking(sdmmc->spiPort, tcmd, 5);
	n = 0x01; /* Dummy CRC + Stop */
	if (cmd == CMD0)
		n = 0x95; /* Valid CRC for CMD0(0) */
	if (cmd == CMD8)
		n = 0x87; /* Valid CRC for CMD8(0x1AA) */

	spi_write_blocking(sdmmc->spiPort, &n, 1);

	/* Receive command resp */
	if (cmd == CMD12)
		spi_read_blocking(sdmmc->spiPort, 0xFF, &res, 1); /* Diacard following one uint8_t when CMD12 */
	n = 10;												  /* Wait for response (10 uint8_ts max) */
	do
	{
		spi_read_blocking(sdmmc->spiPort, 0xFF, &res, 1);
	} while ((res & 0x80) && --n);

	return res; /* Return received response */
}

/*-----------------------------------------------------------------------*/
/* Initialize disk drive                                                 */
/*-----------------------------------------------------------------------*/
uint8_t sdmmc_init(sdmmc_data_t *sdmmc)
{
	uint8_t n, cmd, ty, src, ocr[4];

	sdmmc->Stat = RES_OK;
	// low baudrate
	spi_set_baudrate(sdmmc->spiPort, SPI_BAUDRATE_LOW);
	src = 0xFF;
	sdmmc_spi_cs_low(sdmmc);
	for (n = 10; n; n--)
		spi_write_blocking(sdmmc->spiPort, &src, 1); // Send 80 dummy clocks
	sdmmc_spi_cs_high(sdmmc);

	ty = 0;
	if (sdmmc_send_cmd(CMD0, 0, sdmmc) == 1)
	{ /* Put the card SPI/Idle state, R1 bit0=1*/
		absolute_time_t timeout_time = make_timeout_time_ms(1000);
		if (sdmmc_send_cmd(CMD8, 0x1AA, sdmmc) == 1)
		{													 /* SDv2? */
			spi_read_blocking(sdmmc->spiPort, 0xFF, ocr, 4); // R7(5 uint8_ts): R1 read by sdmmc_send_cmd, Get the other 32 bit return value of R7 resp
			if (ocr[2] == 0x01 && ocr[3] == 0xAA)
			{ /* Is the card supports vcc of 2.7-3.6V? */
				while ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(ACMD41, 1UL << 30, sdmmc))
					; /* Wait for end of initialization with ACMD41(HCS) */
				if ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(CMD58, 0, sdmmc) == 0)
				{ /* Check CCS bit in the OCR */
					spi_read_blocking(sdmmc->spiPort, 0xFF, ocr, 4);
					ty = (ocr[0] & 0x40) ? CT_SDC2 | CT_BLOCK : CT_SDC2; /* Card id SDv2 */
				}
			}
		}
		else
		{ /* Not SDv2 card */
			if (sdmmc_send_cmd(ACMD41, 0, sdmmc) <= 1)
			{ /* SDv1 or MMC? */
				ty = CT_SDC1;
				cmd = ACMD41; /* SDv1 (ACMD41(0)) */
			}
			else
			{
				ty = CT_MMC3;
				cmd = CMD1; /* MMCv3 (CMD1(0)) */
			}
			while ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(cmd, 0, sdmmc))
				;																										   /* Wait for end of initialization */
			if (!(0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) || sdmmc_send_cmd(CMD16, SDMMC_SECT_SIZE, sdmmc) != 0) /* Set block length: 512 */
				ty = 0;
		}
	}
	sdmmc->cardType = ty; /* Card type */
	sdmmc_deselect(sdmmc);
	if (ty)
	{ /* OK */
		// high baudrate
		printf("\nThe actual baudrate(SD/MMC):%d\n",spi_set_baudrate(sdmmc->spiPort, SPI_BAUDRATE_HIGH)); // speed high
		sdmmc->sectSize = SDMMC_SECT_SIZE;
		sdmmc->Stat = RES_OK; /* Clear STA_NOINIT flag */
	}
	else
	{ /* Failed */
		sdmmc->Stat = STA_NOINIT;
	}
	sdmmc->sectCount = sdmmc_get_sector_count(sdmmc);
	return sdmmc->Stat;
}
/////////////////////////////////////////////
/* sdmmc_disk_initialize*/
DSTATUS sdmmc_disk_initialize(sdmmc_data_t *sdmmc)
{
	if (!sdmmc->spiInit)
		sdmmc_init_spi(sdmmc); /* Initialize SPI */
	DSTATUS stat = sdmmc_init(sdmmc);

	return stat;
}
/* sdmmc disk status*/
DSTATUS sdmmc_disk_status(sdmmc_data_t *sdmmc)
{
	return sdmmc->Stat;
}

/* sdmmc disk read*/
DSTATUS sdmmc_disk_read(
	BYTE *buff,	  /* Pointer to the data buffer to store read data */
	LBA_t sector, /* Start sector number (LBA) */
	UINT count,	  /* Number of sectors to read (1..128) */
	sdmmc_data_t *sdmmc)
{
	DWORD sect = (DWORD)(sector);

	if (!count)
		return RES_PARERR; /* Check parameter */
	if (sdmmc->Stat & STA_NOINIT)
		return RES_NOTRDY; /* Check if drive is ready */

	if (!(sdmmc->cardType & CT_BLOCK))
		sect *= 512; /* LBA ot BA conversion (byte addressing cards) */
	if (count == 1)
	{												  /* Single sector read */
		if ((sdmmc_send_cmd(CMD17, sect, sdmmc) == 0) /* READ_SINGLE_BLOCK */
			&& sdmmc_read_datablock(buff, 512, sdmmc))
		{
			count = 0;
		}
		//led_blinking(); //// LED blinking
	}
	else
	{ /* Multiple sector read */
		if (sdmmc_send_cmd(CMD18, sect, sdmmc) == 0)
		{ /* READ_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_read_datablock(buff, 512, sdmmc))
					break;
				buff += 512;
				//led_blinking(); //// LED blinking
			} while (--count);
			
			sdmmc_send_cmd(CMD12, 0, sdmmc); /* STOP_TRANSMISSION */
		}
	}
	//led_blinking_off();  //// LED blinking off
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd()

	return count ? RES_ERROR : RES_OK; /* Return result */
}

DSTATUS sdmmc_disk_write(
	const BYTE *buff, /* Ponter to the data to write */
	LBA_t sector,	  /* Start sector number (LBA) */
	UINT count,		  /* Number of sectors to write (1..128) */
	sdmmc_data_t *sdmmc)
{
	DWORD sect = (DWORD)sector;
	if (!count)
		return RES_PARERR; /* Check parameter */
	if (sdmmc->Stat & STA_NOINIT)
		return RES_NOTRDY; /* Check drive status */
	if (sdmmc->Stat & STA_PROTECT)
		return RES_WRPRT; /* Check write protect */

	if (!(sdmmc->cardType & CT_BLOCK))
		sect *= 512; /* LBA ==> BA conversion (byte addressing cards) */

	if (count == 1)
	{												  /* Single sector write */
		if ((sdmmc_send_cmd(CMD24, sect, sdmmc) == 0) /* WRITE_BLOCK */
			&& sdmmc_write_datablock(buff, 0xFE, sdmmc))
		{
			count = 0;
		}
		//led_blinking();  //// LED_blinking
	}
	else
	{ /* Multiple sector write */
		if (sdmmc->cardType & CT_SDC)
			sdmmc_send_cmd(ACMD23, count, sdmmc); /* Predefine number of sectors */
		if (sdmmc_send_cmd(CMD25, sect, sdmmc) == 0)
		{ /* WRITE_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_write_datablock(buff, 0xFC, sdmmc))
					break;
				buff += 512;

				//led_blinking();  //// LED_blinking
			} while (--count);
			  // LED blinking off
			if (!sdmmc_write_datablock(0, 0xFD, sdmmc))
				count = 1; /* STOP_TRAN token */
		}
	}
	//led_blinking_off();
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd

	return count ? RES_ERROR : RES_OK; /* Return result */
}

/* sdmmc disk ioctl*/
DSTATUS sdmmc_disk_ioctl(
	BYTE cmd,	/* Control command code */
	void *buff, /* Pointer to the conrtol data */
	sdmmc_data_t *sdmmc)
{
	DRESULT res;
	BYTE n, csd[16];
	DWORD st, ed, csize;
	LBA_t *dp;

	BYTE src = 0xFF;

	if (sdmmc->Stat & STA_NOINIT)
		return RES_NOTRDY; /* Check if drive is ready */

	res = RES_ERROR;
	switch (cmd)
	{
	case CTRL_SYNC: /* Wait for end of internal write process of the drive */
		if (sdmmc_select(sdmmc))
			res = RES_OK;
		break;
	case GET_SECTOR_COUNT: /* Get drive capacity in unit of sector (DWORD) */
		if ((sdmmc_send_cmd(CMD9, 0, sdmmc) == 0) && sdmmc_read_datablock(csd, 16, sdmmc))
		{
			if ((csd[0] >> 6) == 1)
			{ /* SDC CSD ver 2 */
				csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1;
				*(LBA_t *)buff = csize << 10;
			}
			else
			{ /* SDC CSD ver 1 or MMC */
				n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
				csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
				*(LBA_t *)buff = csize << (n - 9);
			}
			res = RES_OK;
		}
		break;
	case GET_SECTOR_SIZE: // FF_MAX_SS != FX_MIN_SS
		//*(WORD*)buff=512; // SDHC, SDXC sector size is 512
		*(WORD *)buff = sdmmc->sectSize;

		res = RES_OK;
		break;
	case GET_BLOCK_SIZE: /* Get erase block size in unit of sector (DWORD) */
		if (sdmmc->cardType & CT_SDC2)
		{ /* SDC ver 2+ */
			if (sdmmc_send_cmd(ACMD13, 0, sdmmc) == 0)
			{ /* Read SD status */
				spi_write_blocking(sdmmc->spiPort, &src, 1);
				if (sdmmc_read_datablock(csd, 16, sdmmc))
				{ /* Read partial block */
					for (n = 64 - 16; n; n--)
						spi_write_blocking(sdmmc->spiPort, &src, 1); // xchg_spi(0xFF);	/* Purge trailing data */
					*(DWORD *)buff = 16UL << (csd[10] >> 4);
					res = RES_OK;
				}
			}
		}
		else
		{ /* SDC ver 1 or MMC */
			if ((sdmmc_send_cmd(CMD9, 0, sdmmc) == 0) && sdmmc_read_datablock(csd, 16, sdmmc))
			{ /* Read CSD */
				if (sdmmc->cardType & CT_SDC1)
				{ /* SDC ver 1.XX */
					*(DWORD *)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
				}
				else
				{ /* MMC */
					*(DWORD *)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
				}
				res = RES_OK;
			}
		}
		break;

	case CTRL_TRIM: /* Erase a block of sectors (used when _USE_ERASE == 1) */
		if (!(sdmmc->cardType & CT_SDC))
			break; /* Check if the card is SDC */
		if (sdmmc_disk_ioctl(MMC_GET_CSD, csd, sdmmc))
			break; /* Get CSD */
		if (!(csd[10] & 0x40))
			break; /* Check if ERASE_BLK_EN = 1 */
		dp = buff;
		st = (DWORD)dp[0];
		ed = (DWORD)dp[1]; /* Load sector block */
		if (!(sdmmc->cardType & CT_BLOCK))
		{
			st *= 512;
			ed *= 512;
		}
		if (sdmmc_send_cmd(CMD32, st, sdmmc) == 0 && sdmmc_send_cmd(CMD33, ed, sdmmc) == 0 && sdmmc_send_cmd(CMD38, 0, sdmmc) == 0 && sdmmc_wait_ready(30000, sdmmc))
		{				  /* Erase sector block */
			res = RES_OK; /* FatFs does not check result of this command */
		}
		break;

		/* Following commands are never used by FatFs module */

	case MMC_GET_TYPE: /* Get MMC/SDC type (BYTE) */
		*(BYTE *)buff = sdmmc->cardType;
		res = RES_OK;
		break;

	case MMC_GET_CSD: /* Read CSD (16 bytes) */
		if (sdmmc_send_cmd(CMD9, 0, sdmmc) == 0 && sdmmc_read_datablock((BYTE *)buff, 16, sdmmc))
		{ /* READ_CSD */
			res = RES_OK;
		}
		break;

	case MMC_GET_CID: /* Read CID (16 bytes) */
		if (sdmmc_send_cmd(CMD10, 0, sdmmc) == 0 && sdmmc_read_datablock((BYTE *)buff, 16, sdmmc))
		{ /* READ_CID */
			res = RES_OK;
		}
		break;

	case MMC_GET_OCR: /* Read OCR (4 bytes) */
		if (sdmmc_send_cmd(CMD58, 0, sdmmc) == 0)
		{ /* READ_OCR */
			for (n = 0; n < 4; n++)
				*(((BYTE *)buff) + n) = spi_write_blocking(sdmmc->spiPort, &src, 1); // xchg_spi(0xFF);
			res = RES_OK;
		}
		break;

	case MMC_GET_SDSTAT: /* Read SD status (64 bytes) */
		if (sdmmc_send_cmd(ACMD13, 0, sdmmc) == 0)
		{ /* SD_STATUS */
			spi_write_blocking(sdmmc->spiPort, &src, 1);
			if (sdmmc_read_datablock((BYTE *)buff, 64, sdmmc))
				res = RES_OK;
		}
		break;

	default:
		res = RES_PARERR;
	}

	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd()

	return res;
}


  • msc_device_disk.c

/* this file was modified from tinyUSB example: msc_disk_dual.c*/

/* 
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ha Thach (tinyusb.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */
#include "bsp/board.h"
#include "tusb.h"
#include "stdlib.h"

#include "spi_sdmmc.h"
#include "hardware/gpio.h"
#include "msc_storage_driver.h"

extern sdmmc_data_t *pSDMMC;

void storage_driver_init() {
  if (pSDMMC == NULL) {
      // SDMMC driver initialize
      pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
      pSDMMC->spiInit=false;
#ifdef __SPI_SDMMC_DMA
      pSDMMC->dmaInit=false;
#endif
      sdmmc_disk_initialize(pSDMMC);
  }
  sleep_ms(10);
  // LED blinking when reading/writing
  gpio_init(LED_BLINKING_PIN);
  gpio_set_dir(LED_BLINKING_PIN, true);
}

#if CFG_TUD_MSC

// Invoked to determine max LUN
uint8_t tud_msc_get_maxlun_cb(void)
{
  return 1; // LUN 0: SDMMC, LUN 1: W25Q Flash
}

// Invoked when received SCSI_CMD_INQUIRY
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
{
  switch (lun) {
    case SDMMC_LUN:
      sprintf(vendor_id  , "SDMMC");
      sprintf(product_id , "Mass Storage");
      sprintf(product_rev, "1.0");
    break;
  }
 
}

// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
bool tud_msc_test_unit_ready_cb(uint8_t lun)
{
  //if ( lun == 1 && board_button_read() ) return false;

  return true; // RAM disk is always ready
}

// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
// Application update block count and block size
void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size)
{
  switch(lun) {
    case SDMMC_LUN:
        *block_count = pSDMMC->sectCount;
        *block_size  = pSDMMC->sectSize;
    break;

  }
}

// Invoked when received Start Stop Unit command
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
{
  
  (void) lun;
  (void) power_condition;

  if ( load_eject )
  {
    if (start)
    {
printf("start\n");
      // load disk storage
    }else
    {
printf("stop\n");
      // unload disk storage
    }
  }

  return true;
}

// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
{
  switch(lun) {
    case SDMMC_LUN:
      if (!msc_sdmmc_read_sector(lba, buffer, bufsize, pSDMMC)) return -1;
    break;

  }
  return (int32_t) bufsize;
}

bool tud_msc_is_writable_cb (uint8_t lun)
{
  (void) lun;

#ifdef CFG_EXAMPLE_MSC_READONLY
  return false;
#else
  return true;
#endif
}

// Callback invoked when received WRITE10 command.
// Process data in buffer to disk's storage and return number of written bytes
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize)
{

  switch (lun) {
    case SDMMC_LUN:
        if (!msc_sdmmc_write_sector(lba, buffer, bufsize, pSDMMC)) return -1;
    break;
  }
  

  return (int32_t) bufsize;
}

// Callback invoked when received an SCSI command not in built-in list below
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
// - READ10 and WRITE10 has their own callbacks
int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize)
{
  // read10 & write10 has their own callback and MUST not be handled here

  void const* response = NULL;
  int32_t resplen = 0;

  // most scsi handled is input
  bool in_xfer = true;

  switch (scsi_cmd[0])
  {
    default:
      // Set Sense = Invalid Command Operation
      tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);

      // negative means error -> tinyusb could stall and/or response with failed status
      resplen = -1;
    break;
  }

  // return resplen must not larger than bufsize
  if ( resplen > bufsize ) resplen = bufsize;

  if ( response && (resplen > 0) )
  {
    if(in_xfer)
    {
      memcpy(buffer, response, (size_t) resplen);
    }else
    {
      // SCSI output
    }
  }

  return resplen;
}

#endif

void led_blinking_task(void)
{
  static uint32_t start_ms = 0;
  static bool led_state = false;

  // Blink every interval ms
  if ( board_millis() - start_ms < 50) return; // not enough time
  start_ms += 50;

  gpio_put(LED_BLINKING_PIN,led_state);
  led_state = 1 - led_state; // toggle
}

void led_blinking_task_off(void) {
  gpio_put(LED_BLINKING_PIN,false);
}

  • main.c


#include 
#include 
#include 
#include "pico/stdlib.h"

#include "bsp/board.h"
#include "tusb.h"

#include "msc_storage_driver.h"

#include "pico_storage.h"
#include "hardware/rtc.h"
#include "pico_audio_recorder.h"


/*------------- MAIN -------------*/
int main(void)
{
  stdio_init_all();
  rtc_init();

  datetime_t t = {
    .year=2023, .month=10, .day=29, 
    .dotw=6,
    .hour=14, .min=05, .sec=50
  }; 
  
   if (!rtc_set_datetime(&t)) printf("set rtc error\n");
     
  storage_driver_init();
  
  board_init();
  // init device stack on configured roothub port
  tud_init(BOARD_TUD_RHPORT);

  inmp441_pio_init(INMP441_pio, INMP441_SM, INMP441_SD, INMP441_SCK);

  while (1)
  {
      if (get_recorder_state() == STATE_START_RECORDING) {
        printf("Start recording\n");
        inmp441_starting_recording_to_file_wav();
      }
    
             
      tud_task(); // tinyusb device task
      tight_loop_contents();
  }

  return 0;
}

  • CMakeLists.txt(root)

# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(audio_recorder C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(audio_recorder 
        main.c 
        usb_descriptors.c)

pico_set_program_name(audio_recorder "pico_audio_recotr")
pico_set_program_version(audio_recorder "0.1")

pico_enable_stdio_uart(audio_recorder 1)
pico_enable_stdio_usb(audio_recorder 0)

# Add the standard library to the build
target_link_libraries(audio_recorder
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(audio_recorder PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
target_link_libraries(audio_recorder 
        hardware_dma
        hardware_pio
        pico_multicore
        pico_util
        )
add_subdirectory(storage_driver)
# Add any user requested libraries
target_link_libraries(audio_recorder 
        tinyusb_device 
        tinyusb_board
        storage_driver
        )
add_subdirectory(pico_audio_recorder)
# Add any user requested libraries
target_link_libraries(audio_recorder 
               pico_audio_recorder
        )

pico_add_extra_outputs(audio_recorder)