prettyprint

2022年5月9日 星期一

Raspberry Pi 3 + FreeBSD + ZFS + iSCSI (Part 1)

實驗在Raspberry pi 3 B+上安裝FreeBSD作業系統,啟用ZFS檔案系統,比較ZFS dataset有使用壓縮(Compression)與副本(copies)屬性使用磁碟空間比較。另外建立一個ZFS Volume(Block device)作為iSCSI Target使用。以Windows系統連線iSCSI Target並格式化為NTFS。測試ZFS snapshot, rollback 等功能。 

一、下載Raspberry Pi的FreeBSD OS Image:

從FreeBSD官網下載作業系統映像檔,如下圖所示:

下載檔案:FreeBSD-13.0-RELEASE-arm64-aarch64-RPI.img.xz


二、使用Raspberry Pi Imager安裝作業系統到SD Card上:
  1. 從https://www.raspberrypi.com/software/下載Raspberry Pi Imager:
  2. 安裝FreeBSD Image:
    選用use custom選項,安裝剛剛下載的OS映像檔。

三、設定FreeBSD Static IP, nameserver, 與sshd允許遠端連線:

  1. static IP: 查看網路面卡名稱(ue0),編輯/etc/rc.conf加入
    ipconfig_ue0="inet x.x.x.x netmask x.x.x.x"
    defaultrouter="x.x.x.x"

  2. nameserver:編輯/etc/resolv.conf加入nameserver ip:

  3. sshd:編輯/etc/ssh/sshd_config更改permitRootLogin成yes

四、把外接USB硬碟加入ZFS pool:

啟用ZFS服務:
  1. 編輯/etc/rc.conf加入zfs_enable="YES"

  2. 執行service zfs start啟用zfs service
  3. 查看外接USB硬碟設備代號(da0),
    執行zpool create storage da0建立名稱為storage的zfs pool。

五、比較zfs dataset具有compression與copies屬性儲存空間的差異

  1. 分別建立storage/normal, storage/compress與storage/data三個filesystem,

  2. 設定storage/compress具有compression屬性
  3. 設定storage/data具有copies=2屬性,可把資料儲存兩份,增加可靠性
  4. 複製相同的資料到這三個檔案系統,查看使用硬碟空間的差異。



    六、建立iSCSI Target,使用Windows iSCSI Initiator 連線成為自己的硬碟

  1. 建立zfs volume(block device)作為iSCSI Target設備 zfs create -V 25G storage/iscsi01
  2. 編輯/etc/ctl.conf
    更改etc.conf權限,chmod 644 /etc/ctl.conf
    編輯/etc/rc.conf加入ctld_enable="YES"
    啟用service ctld start

  3. 啟用Windows iSCSI Initiator 連線iSCSI Target

  4. 使用Windoes disk managment格式化iSCSI 硬碟

七、zfs snapshot與rollback展示:

2022年4月15日 星期五

ESP-IDF: Using ESP-IDF Classic BT SPP API to implement ESP32 Bluetooth SPP Server and Client devices

 本實驗使用ESP-IDF Classic BT API來製作含有5個藍芽設備形成的piconet,一個ESP32-WROOM-32作為Master,兩個ESP32(ESP32-S, ESP32-CAM)為Slave,一台Android phone 為Slave執行Bluetooth Serial Terminal當作控制台,即時接受piconet中元件連線狀態,另外一台筆電為Slave執行Bluetooth Serial Terminal。Slave可向Master發送控制命令,或藉由Master向其他Slave發送控制命令或文字訊息。

本實驗共訂出5個指令類型:

  1. AT+ST<device_name>:向Master註冊自已的名稱<device_name>,例如:AT+STP0
  2. AT+LS: 列出已連線的Slaves,,例如:AT+LS。
  3. AT+CMD<target_device_name:command>:向其他device送出命令,例如:AT+CMDC0:ON
  4. AT+TXT<target_device_name:TEXT>:向其他device送出Text,例如:AT+TXTT0:TEST
  5. AT+DIS: 向Master送出離線訊息,,例如:AT+DIS。
本實驗Master名稱固定為M0, 擔任控制台的Slave指定為P0。如下圖所示:


一、成果展示影片:



二、使用軟體環境:

  1. VSCode
  2. ESP-IDF extension
  3. Bluetooth serial terminal for Android and for Windows

三、ESP-IDF Classic BT API - GAP & SPP:

SPP Bluetooth Stack:
BT GAP API提供設備間配對連線等,BT SPP API提供Serial Port Service(Server or client)等。
ESP32 Master與Slave端程式碼大同小異,一端負責提供SPP Service Server,另一端為SPP Client。
有關ESP藍芽架構主要參考文件為下列官方網址:

GAP與SPP以register callback function 來處理相對應的Event,API相關文件說明如下連結。

詳細程式碼如下所示,完整程式說明請觀看成果展示影片。

四、程式碼:

1. Server端(Run at Master side)
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt_defs.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_spp_api.h"
#include "esp_err.h"
#include "esp_log.h"
#include "string.h"
#include "driver/gpio.h"


#define TAG "MY_SPP_MASTER"
#define BT_DEVICE_NAME  "MY_SPP_MASTER"
#define SPP_SERVER_1 "MY_SPP_SERVER"
#define CLIENT_NAME_MAX 10

QueueHandle_t spp_queue_handle;
char rmt_bd_name[ESP_BT_GAP_MAX_BDNAME_LEN+1];
// slave device link list
typedef struct _tbd{
    uint32_t handle;
    uint8_t bd_client_name[CLIENT_NAME_MAX+1];
    uint8_t len;
    struct _tbd *next;
} bd_client_t;
bd_client_t *bd_link_header=NULL;

//input data item
typedef struct {
    uint32_t handle;
    uint16_t len;
    uint8_t in_data[ESP_SPP_MAX_MTU];
} spp_queue_item_t;
spp_queue_item_t spp_queue_item;

bool pannel_connected=false;
uint32_t pannel_handle = 0;

void my_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
    switch(event) {
        case ESP_BT_GAP_DISC_RES_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_DISC_RES_EVT");
        break;
        case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_DISC_STATE_CHANGED_EVT");
        break;
        case ESP_BT_GAP_RMT_SRVCS_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_RMT_SRVCS_EVT");
        break; 
        case ESP_BT_GAP_RMT_SRVC_REC_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_RMT_SRVC_REC_EVT");
        break;
        case ESP_BT_GAP_AUTH_CMPL_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_AUTH_CMPL_EVT");
            if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
                ESP_LOGI(TAG, "bda:%x%x%x%x%x%x, device_name:%s--", 
                param->auth_cmpl.bda[0],param->auth_cmpl.bda[1],param->auth_cmpl.bda[2],param->auth_cmpl.bda[3],param->auth_cmpl.bda[4],param->auth_cmpl.bda[5],
                (char*)param->auth_cmpl.device_name);
            }
        break;
        case ESP_BT_GAP_PIN_REQ_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_PIN_REQ_EVT");
        break;
        case ESP_BT_GAP_CFM_REQ_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_CFM_REQ_EVT");
            esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
        break;
        case ESP_BT_GAP_KEY_NOTIF_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_KEY_NOTIF_EVT");
        break;
        case ESP_BT_GAP_KEY_REQ_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_KEY_REQ_EVT");
        break;
        case ESP_BT_GAP_READ_RSSI_DELTA_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_READ_RSSI_DELTA_EVT");
        break;
        case ESP_BT_GAP_CONFIG_EIR_DATA_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_CONFIG_EIR_DATA_EVT");
        break;
        case ESP_BT_GAP_SET_AFH_CHANNELS_EVT:
            ESP_LOGI(TAG, "");
        break;
        case ESP_BT_GAP_READ_REMOTE_NAME_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_READ_REMOTE_NAME_EVT");
        break;
        case ESP_BT_GAP_MODE_CHG_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_MODE_CHG_EVT");
        break;
        case ESP_BT_GAP_REMOVE_BOND_DEV_COMPLETE_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_REMOVE_BOND_DEV_COMPLETE_EVT");
        break;
        case ESP_BT_GAP_QOS_CMPL_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_QOS_CMPL_EVT");
        break;
        default:
        break;
    }
}
char msg[100];
char temp_name[30];
void my_bt_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
    switch(event) {
        case ESP_SPP_INIT_EVT:
            ESP_LOGI(TAG, "ESP_SPP_INIT_EVT");
        break;
        case ESP_SPP_UNINIT_EVT:
            ESP_LOGI(TAG, "ESP_SPP_UNINIT_EVT");
        break;
        case ESP_SPP_DISCOVERY_COMP_EVT:
            ESP_LOGI(TAG, "ESP_SPP_DISCOVERY_COMP_EVT");
        break;
        case ESP_SPP_OPEN_EVT:
            ESP_LOGI(TAG,"ESP_SPP_OPEN_EVT");
        break;
        case ESP_SPP_CLOSE_EVT:
            ESP_LOGI(TAG, "close handle:%d, status:%d", param->close.handle, param->close.status);
            spp_queue_item.handle = param->close.handle;
            spp_queue_item.len = 6;
            memcpy(spp_queue_item.in_data, (uint8_t*)"AT+DIS", 6);
            xQueueSend(spp_queue_handle, &spp_queue_item, portMAX_DELAY);
        break;
        case ESP_SPP_START_EVT:
            ESP_LOGI(TAG, "SPP_START_EVENT:status:%d, scn:%d", param->start.status, param->start.scn);
        break;
        case ESP_SPP_CL_INIT_EVT:
            ESP_LOGI(TAG, "ESP_SPP_CL_INIT_EVT");
        break;
        case ESP_SPP_DATA_IND_EVT:
            spp_queue_item.handle = param->data_ind.handle;
            spp_queue_item.len = param->data_ind.len;
            memcpy(spp_queue_item.in_data, param->data_ind.data, param->data_ind.len);
            if (spp_queue_item.in_data[spp_queue_item.len-2] == (uint8_t)'\r' && spp_queue_item.in_data[spp_queue_item.len-1] == (uint8_t)'\n') {
                spp_queue_item.len -= 2;
            }
            xQueueSend(spp_queue_handle, &spp_queue_item, portMAX_DELAY);
        break;
        case ESP_SPP_CONG_EVT:
            ESP_LOGI(TAG, "cong handle:%d, cong:%d, status:%d", param->cong.handle, param->cong.cong, param->cong.status);
        break;
        case ESP_SPP_WRITE_EVT:
            ESP_LOGI(TAG, "ESP_SPP_WRITE_EVT");
        break;
        case ESP_SPP_SRV_OPEN_EVT:
            ESP_LOGI(TAG, "ESP_SPP_SRV_OPEN_EVT:rem_bda:%x:%x:%x:%x:%x:%x",
            param->srv_open.rem_bda[0],param->srv_open.rem_bda[1],param->srv_open.rem_bda[2],param->srv_open.rem_bda[3],param->srv_open.rem_bda[4],param->srv_open.rem_bda[5]);
            ESP_LOGI(TAG, "handle:%d, new handle:%d", param->srv_open.handle, param->srv_open.new_listen_handle);
        break;
        case ESP_SPP_SRV_STOP_EVT:
            ESP_LOGI(TAG, "ESP_SPP_SRV_STOP_EVT");
        break;
        default:
        break;
    }
}

void set_bd_client(spp_queue_item_t item) {
    bd_client_t *client;
    bd_client_t *end = bd_link_header;

    client = malloc(sizeof(bd_client_t));
    client->handle = item.handle;
    memcpy(client->bd_client_name, item.in_data+5, item.len-5);
    if (memcmp(client->bd_client_name, (uint8_t*)"P0", item.len-5)==0) {
        pannel_connected=true;
        pannel_handle = item.handle;
    }
    client->len = item.len-5;
    client->next=NULL;
    if(end == NULL) {
        bd_link_header = client;
    } else {
        while(end) {
            if (memcmp(end->bd_client_name, item.in_data+5, item.len-5) == 0) {
                end->handle=client->handle;
                free(client);
                break;
            } else if (end->next == NULL) {
                end->next = client;
                if (pannel_handle) {
                    memcpy((uint8_t*)msg, client->bd_client_name, client->len);
                    sprintf(msg+client->len, ":Connected");
                    esp_spp_write(pannel_handle, client->len+10, (uint8_t*)msg);
                }
                break;
            }
            end = end->next;
        }
    }
}

void show_bd_connected_client(spp_queue_item_t item) {
    bd_client_t *end = bd_link_header;
    while (end) {
        esp_spp_write(item.handle, end->len, end->bd_client_name);
        vTaskDelay(20/portTICK_PERIOD_MS);
        esp_spp_write(item.handle, 2, (uint8_t*)"\r\n");
        vTaskDelay(20/portTICK_PERIOD_MS);
        end = end->next;
    }
}

void get_bd_client_name(uint32_t handle, char *name) {
    bd_client_t *end = bd_link_header;
    
    while (end) {
        if ((end->handle) == handle) {
            memcpy((uint8_t*)name, end->bd_client_name, end->len);
            name[end->len] = '\0';
            break;
        }
        end=end->next;
    }
    name=NULL;
}
void bd_send_cmd(spp_queue_item_t item) {
    bd_client_t *end = bd_link_header;
    int colon=0;
    for (int i=0; i < item.len; i++) {
        if (item.in_data[i] == (uint8_t)':') {
            colon = i;
            break;
        }
    }
    if (colon == 0) return;
    if (memcmp(item.in_data+6, (uint8_t*)"M0",colon-6)==0) { // M0 is default master device name
        if (memcmp(item.in_data+colon+1, (uint8_t*)"ON", item.len-(colon+1)) == 0) {
            gpio_set_level(GPIO_NUM_4, 1);
        }
        if (memcmp(item.in_data+colon+1, (uint8_t*)"OFF", item.len-(colon+1)) == 0) {
            gpio_set_level(GPIO_NUM_4, 0);
        }
    } else {
        while(end) {
            if (memcmp(end->bd_client_name, item.in_data+6, end->len)==0) {
                esp_spp_write(end->handle, item.len-(colon+1), item.in_data+colon+1);
                break;
            }
            end=end->next;
        }
    }
}

void bd_send_txt(spp_queue_item_t item) {
    bd_client_t *end = bd_link_header;
    int colon=0;
    for (int i=0; i < item.len; i++) {
        if (item.in_data[i] == (uint8_t)':') {
            colon = i;
            break;
        }
    }
    if (colon == 0) return;
    char name[10];
    while(end) {
        memcpy((uint8_t*)name, end->bd_client_name, end->len);
        name[end->len]='\0';
        printf("%s:%d\n", name, end->handle);
        if (memcmp(end->bd_client_name, item.in_data+6, end->len)==0) {
            esp_spp_write(end->handle, item.len-(colon+1), item.in_data+colon+1);
            break;
        }
        end=end->next;
    }
    
}

void bd_disconnect(spp_queue_item_t item) {
    bd_client_t *pre;
    bd_client_t *end = bd_link_header;
    
    get_bd_client_name(item.handle, temp_name);

    if (strcmp(temp_name, "P0")==0) {
        pannel_connected = false;
        pannel_handle=0;
    }
    pre = bd_link_header;
    while (end) {
        if (item.handle == end->handle) {
            pre->next = end->next;
            if (pannel_connected && pannel_handle) {
                memcpy((uint8_t*)msg, end->bd_client_name, end->len);
                sprintf(msg+end->len, ":Disconnected");
                esp_spp_write(pannel_handle, end->len+13, (uint8_t*)msg);
            }
            if (end == bd_link_header) {
                bd_link_header = end->next;
            } else {
                pre->next = end->next;
            }
            free(end);
            break;
        }
        pre = end;
        end = end->next;
    }
}
void QueueReceiveTask(void* param) {
    spp_queue_item_t item;
    while(1) {
        if (xQueueReceive(spp_queue_handle, &item, portMAX_DELAY) == pdTRUE) {
            item.in_data[item.len]='\0';
            if (memcmp(item.in_data, (uint8_t*)"AT+ST", 5) == 0) {
                set_bd_client(item);
            } else if (memcmp(item.in_data, (uint8_t*)"AT+LS", 5) == 0) {
                show_bd_connected_client(item);
            } else if (memcmp(item.in_data, (uint8_t*)"AT+CMD", 6) == 0) {
                bd_send_cmd(item);
            } else if (memcmp(item.in_data, (uint8_t*)"AT+TXT", 6) == 0) {
                bd_send_txt(item);
            } else if (memcmp(item.in_data, (uint8_t*)"AT+DIS", 6) == 0) {
                bd_disconnect(item);
            }
        }   
        vTaskDelay(10);
    } 
}

void app_main(void)
{
    esp_err_t err;
    err = nvs_flash_init();
    if (err != ESP_OK) {
        ESP_LOGI(TAG, "nvs flash init error");
        return;
    }
 
    gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
    gpio_set_level(GPIO_NUM_4, 0);
    spp_queue_handle =  xQueueCreate(10, sizeof(spp_queue_item_t));
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    err = esp_bt_controller_init(&bt_cfg);
    err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);

    err = esp_bluedroid_init();
    err = esp_bluedroid_enable();
    
    err = esp_spp_register_callback(my_bt_spp_cb);
    err = esp_spp_init(ESP_SPP_MODE_CB);

    err = esp_spp_start_srv(ESP_SPP_SEC_AUTHENTICATE, ESP_SPP_ROLE_MASTER, 1, SPP_SERVER_1);

    err = esp_bt_gap_register_callback(my_bt_gap_cb);
    
    esp_bt_cod_t cod;
    cod.major = 0x1;
    cod.minor=0x0;
    err = esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_MAJOR_MINOR);

    esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_NONE;
    err = esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &iocap, sizeof(esp_bt_io_cap_t));
    err = esp_bt_dev_set_device_name(BT_DEVICE_NAME);
    err = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);

    esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
    esp_bt_pin_code_t pin_code;
    esp_bt_gap_set_pin(pin_type, 0, pin_code);

    xTaskCreatePinnedToCore(QueueReceiveTask, "TASK", 5120, 0, 10,0,1);
 }

2. Client端(Run at Slave side)
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_spp_api.h"
#include "esp_err.h"
#include "esp_log.h"
#include "string.h"
#include "driver/gpio.h"


#define TAG "MY_SPP_C1"
#define BT_DEVICE_NAME  "MY_SPP_C1"
uint8_t client_name[7]="AT+STC1";
#define SPP_SERVER_NAME "MY_SPP_MASTER"
uint8_t *spp_peer_name;

esp_bd_addr_t spp_server_addr = {0};
uint32_t hServer=0;


QueueHandle_t spp_queue_handle;

typedef struct {
    uint32_t handle;
    uint16_t len;
    uint8_t in_data[ESP_SPP_MAX_MTU];
} spp_queue_item_t;

spp_queue_item_t spp_queue_item;

uint8_t bd_name_len;

void my_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
    switch(event) {
        case ESP_BT_GAP_DISC_RES_EVT:
            for (int i = 0 ; i < param->disc_res.num_prop; i++) {
                if (param->disc_res.prop[i].type == ESP_BT_GAP_DEV_PROP_EIR) {
                    spp_peer_name = esp_bt_gap_resolve_eir_data(param->disc_res.prop[i].val, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &bd_name_len);
                    if (!spp_peer_name) {
                        spp_peer_name = esp_bt_gap_resolve_eir_data(param->disc_res.prop[i].val, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &bd_name_len);
                    }
                    if (spp_peer_name) {
                        if (bd_name_len > ESP_BT_GAP_MAX_BDNAME_LEN) bd_name_len = ESP_BT_GAP_MAX_BDNAME_LEN;
                        spp_peer_name[bd_name_len]='\0';
                        // connect to default MASTER: MY_SPP_MASTER
                        if (strncmp(SPP_SERVER_NAME, (char*)spp_peer_name, bd_name_len) == 0) {
                            esp_bt_gap_cancel_discovery();
                            memcpy(spp_server_addr, param->disc_res.bda, ESP_BD_ADDR_LEN);
                            esp_spp_start_discovery(spp_server_addr);
                        }
                    } else {
                        ESP_LOGI(TAG, "GAP_DISC_RES, no peer_name, len:%d", bd_name_len);
                    }
                }
            }
        break;
        case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
            ESP_LOGI(TAG, "GAP_DISC_STATE_CHANGE");
        break;
        case ESP_BT_GAP_RMT_SRVCS_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_RMT_SRVCS_EVT");
        break; 
        case ESP_BT_GAP_RMT_SRVC_REC_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_RMT_SRVC_REC_EVT");
        break;
        case ESP_BT_GAP_AUTH_CMPL_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_AUTH_CMPL_EVT");
        break;
        case ESP_BT_GAP_PIN_REQ_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_PIN_REQ_EVT");
        break;
        case ESP_BT_GAP_CFM_REQ_EVT:
            esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
        break;
        case ESP_BT_GAP_KEY_NOTIF_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_KEY_NOTIF_EVT");
        break;
        case ESP_BT_GAP_KEY_REQ_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_KEY_REQ_EVT");
        break;
        case ESP_BT_GAP_READ_RSSI_DELTA_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_READ_RSSI_DELTA_EVT");
        break;
        case ESP_BT_GAP_CONFIG_EIR_DATA_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_CONFIG_EIR_DATA_EVT");
        break;
        case ESP_BT_GAP_SET_AFH_CHANNELS_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_SET_AFH_CHANNELS_EVT");
        break;
        case ESP_BT_GAP_READ_REMOTE_NAME_EVT:
            ESP_LOGI(TAG, "remote Name:%s",(char*)param->read_rmt_name.rmt_name);
        break;
        case ESP_BT_GAP_MODE_CHG_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_MODE_CHG_EVT");
        break;
        case ESP_BT_GAP_REMOVE_BOND_DEV_COMPLETE_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_REMOVE_BOND_DEV_COMPLETE_EVT");
        break;
        case ESP_BT_GAP_QOS_CMPL_EVT:
            ESP_LOGI(TAG, "ESP_BT_GAP_QOS_CMPL_EVT");
        break;
        default:
        break;
    }
}

void my_bt_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
    switch(event) {
        case ESP_SPP_INIT_EVT:
            if (param->init.status == ESP_SPP_SUCCESS) {
                esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
                esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 0x10, 0);
            } else {
                ESP_LOGI(TAG, "ESP_SPP_INIT_ERROR");
            }
        break;
        case ESP_SPP_UNINIT_EVT:
        break;
        case ESP_SPP_DISCOVERY_COMP_EVT:
            // connect to spp server if found
            if (param->disc_comp.status == ESP_SPP_SUCCESS) {
                if (esp_spp_connect(ESP_SPP_SEC_AUTHORIZE, ESP_SPP_ROLE_SLAVE, 1, spp_server_addr)!=ESP_OK) {
                    ESP_LOGI(TAG, "esp_spp_connect error");
                }
            } else {
                ESP_LOGI(TAG, "ESP_SPP_DISCOVERY_COMP_EVT: not found retry...");
                esp_spp_start_discovery(spp_server_addr);
            }
        break;
        case ESP_SPP_OPEN_EVT:
             if (param->open.status == ESP_SPP_SUCCESS) {
                hServer=param->open.handle;
                esp_spp_write(param->open.handle, 7, (uint8_t*)client_name);
             } else {
                 ESP_LOGI(TAG, "CLINET OPEN- failure");
             }
        break;
        case ESP_SPP_CLOSE_EVT:
            ESP_LOGI(TAG, "close handle:%d, status:%d", param->close.handle, param->close.status);
        break;
        case ESP_SPP_START_EVT:
            ESP_LOGI(TAG, "SPP_START_EVENT:status:%d, scn:%d", param->start.status, param->start.scn);
        break;
        case ESP_SPP_CL_INIT_EVT:
            ESP_LOGI(TAG, "Clinet init");
        break;
        case ESP_SPP_DATA_IND_EVT:
            spp_queue_item.handle = param->data_ind.handle;
            spp_queue_item.len = param->data_ind.len;
            memcpy(spp_queue_item.in_data, param->data_ind.data, param->data_ind.len);
            xQueueSend(spp_queue_handle, &spp_queue_item, portMAX_DELAY);
        break;
        case ESP_SPP_CONG_EVT:
            ESP_LOGI(TAG, "cong handle:%d, cong:%d, status:%d", param->cong.handle, param->cong.cong, param->cong.status);
        break;
        case ESP_SPP_WRITE_EVT:
            ESP_LOGI(TAG, "ESP_SPP_WRITE_EVT");
        break;
        case ESP_SPP_SRV_OPEN_EVT:
        ESP_LOGI(TAG, "ESP_SPP_SRV_OPEN_EVT");
        break;
        case ESP_SPP_SRV_STOP_EVT:
        ESP_LOGI(TAG, "ESP_SPP_SRV_STOP_EVT");
        break;
        default:
        break;
    }
}

void QueueReceiveTask(void* param) {
    spp_queue_item_t item;
    while(1) {
        if (xQueueReceive(spp_queue_handle, &item, portMAX_DELAY) == pdTRUE) {
            if (memcmp(item.in_data, (uint8_t*)"ON", 2) == 0) {
                gpio_set_level(GPIO_NUM_4, 1);
            }
            if (memcmp(item.in_data, (uint8_t*)"OFF", 3) == 0) {
                gpio_set_level(GPIO_NUM_4, 0);
            }
        }   
        vTaskDelay(10);
    } 
}

void app_main(void)
{
    esp_err_t err;
    err = nvs_flash_init();
    if (err != ESP_OK) {
        ESP_LOGI(TAG, "nvs flash init error");
        return;
    }
    gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
    spp_queue_handle =  xQueueCreate(10, sizeof(spp_queue_item_t));
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    err = esp_bt_controller_init(&bt_cfg);
    err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);

    err = esp_bluedroid_init();
    err = esp_bluedroid_enable();
    
    err = esp_bt_gap_register_callback(my_bt_gap_cb);
    esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_NONE;
    err = esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &iocap, sizeof(esp_bt_io_cap_t));
    err = esp_bt_dev_set_device_name(BT_DEVICE_NAME);
   
    err = esp_spp_register_callback(my_bt_spp_cb);
    err = esp_spp_init(ESP_SPP_MODE_CB);

    xTaskCreatePinnedToCore(QueueReceiveTask, "TASK", 4096, 0, 1,0,1);
 }

2022年4月6日 星期三

ESP-IDF: ESP32 Bluetooth stereo Speaker with Backward/Play(Pause)/Forward button

本實驗製作一個藍芽喇叭,並附有前一首/下一首,播放/暫停控制鍵。

一、使用元件:

  1. ESP32-S(38 pins)開發版
  2. MAX98357A
  3. 3W喇叭
  4. 按鈕

二、成果展示:




三、軟體開發環境:

  1. VS Code
  2. ESP-IDF extension

四、Bluetooth classic Audio:

ESP32 Bluetooth 架構主要參考文件為下列官方網址:
https://www.espressif.com/sites/default/files/documentation/esp32_bluetooth_architecture_en.pdf
bluetooth audio 使用bluetooth classic A2DP and AVRCP因此只能使用Bluedroid,架構如下
 
memuconfig的bluetooth選項如下:

bluetooth audio 使用A2DP與AVRCP remote control,其profile相關如下圖所示:
依此結構,軟體初始化順序為
controller: init/enable --> bluedroid init/enable --> AVRCP register callback/init --> A2DP register cb --> A2DP sink register cb and data cb/init --> GAP register cb, security param, set device name and scan mode:詳細程式碼如文末所附程式碼(或成果展示影片的詳細步驟)。相關API參考網址:
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/classic_bt.html

五、ESP32 使用的GPIO Pins:

  1. Buttons and LEDs
  2. I2S pins:

六、程式碼:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_bt.h"
#include "esp_bt_device.h"
#include "esp_bt_main.h"
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#include "esp_gap_bt_api.h"
#include "string.h"
#include "nvs_flash.h"
#include "driver/i2s.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define BUTTON_FORWARD_PIN           5
#define BUTTON_PLAY_PAUSE_PIN        23
#define BUTTON_BACKWARD_PIN          19
#define LED_RED_PIN                  17
#define LED_GREEN_PIN                16
#define TAG "MY_BT_SPEAKER"

uint8_t my_bt_speaker_state=ESP_AVRC_PLAYBACK_STOPPED;
const char bt_device_name[]="MY_BT_SPEAKER";
bool avrc_conn=false;

static bool ct_button_play_pause_press=false;
static bool ct_button_backward_press=false;
static bool ct_button_forward_press=false;

void my_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
    switch (event) {
        case ESP_BT_GAP_DISC_RES_EVT:
        break;
        case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:
        break;
        case ESP_BT_GAP_RMT_SRVCS_EVT:
        break;
        case ESP_BT_GAP_RMT_SRVC_REC_EVT:
        break;
        case ESP_BT_GAP_AUTH_CMPL_EVT:
            ESP_LOGI(TAG,"auth: state:%d, remote_name:%s", param->auth_cmpl.stat, param->auth_cmpl.device_name);
        break;
        case ESP_BT_GAP_PIN_REQ_EVT:
        break;
        case ESP_BT_GAP_CFM_REQ_EVT:
            esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
        break;
        case ESP_BT_GAP_KEY_NOTIF_EVT:
        break;
        case ESP_BT_GAP_MODE_CHG_EVT:
        break;
        default: 
            ESP_LOGI(TAG, "GAP EVENT ID:%d",event);
        break;
    }
}


void my_bt_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) {
    switch(event) {
        case ESP_A2D_CONNECTION_STATE_EVT:
        if (param->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
            esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
            gpio_set_level(LED_GREEN_PIN, 1);
            gpio_set_level(LED_RED_PIN, 0);
        }
        if (param->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
            esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
            gpio_set_level(LED_GREEN_PIN, 0);
            gpio_set_level(LED_RED_PIN, 1);
        }
        break;
        case ESP_A2D_AUDIO_STATE_EVT:
        break;
        case ESP_A2D_AUDIO_CFG_EVT:
        if (param->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) {
            int sample_rate = 16000;
            char oct0 = param->audio_cfg.mcc.cie.sbc[0];
            if (oct0 & (0x01 << 6)) {
                sample_rate = 32000;
            } else if (oct0 & (0x01 << 5)) {
                sample_rate = 44100;
            } else if (oct0 & (0x01 << 4)) {
                sample_rate = 48000;
            }
            i2s_set_clk(0, sample_rate, 16, 2);
        }
            
        break;
        case ESP_A2D_MEDIA_CTRL_ACK_EVT:
        break;
        case ESP_A2D_PROF_STATE_EVT:
        break;
        default:
        break;
    }
}

void my_bt_a2d_sink_data_cb(const uint8_t *buf, uint32_t len) {
    size_t wb;
    i2s_write(0, buf, len, &wb, portMAX_DELAY);
}

esp_avrc_rn_evt_cap_mask_t rn_cap_mask={0};
void my_bt_avrc_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) {
    char *text;
    switch(event) {
        case ESP_AVRC_CT_CONNECTION_STATE_EVT:
        if (param->conn_stat.connected) {
            esp_avrc_ct_send_get_rn_capabilities_cmd(1);
            avrc_conn=1;
            esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
        } else {
            rn_cap_mask.bits=0;
             avrc_conn=0;
        }
        break;
        case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
        break;
        case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
            switch(param->change_ntf.event_id) {
                case ESP_AVRC_RN_VOLUME_CHANGE:
                break;
                case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
                    my_bt_speaker_state = param->change_ntf.event_parameter.playback;
                break;
                default:
                break;
            }
        break;
        case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
            rn_cap_mask.bits = param->get_rn_caps_rsp.evt_set.bits;
            break;
        case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT:
        break;
        case ESP_AVRC_CT_METADATA_RSP_EVT:
            text = (char*)param->meta_rsp.attr_text;
            text[param->meta_rsp.attr_length]='\0';
            ESP_LOGI(TAG,"metadata_rsp:%d, %s, %d", param->meta_rsp.attr_id, text, param->meta_rsp.attr_length);
        break;
        default:
        break;
    }
}
static void ct_play_forward(void *param) {
    if (!ct_button_forward_press) {
        ct_button_forward_press=true;
    }
}
static void ct_play_play_pause(void *param) {
    if (!ct_button_play_pause_press) {
        ct_button_play_pause_press=true;
    }
}
static void ct_play_backward(void *param) {
    if (!ct_button_backward_press) {
        ct_button_backward_press=true;
    }
}
void my_bt_set_gpio_pins() {
    gpio_set_direction(BUTTON_BACKWARD_PIN, GPIO_MODE_INPUT);
    gpio_set_direction(BUTTON_FORWARD_PIN, GPIO_MODE_INPUT);
    gpio_set_direction(BUTTON_PLAY_PAUSE_PIN, GPIO_MODE_INPUT);
    gpio_set_direction(LED_GREEN_PIN, GPIO_MODE_OUTPUT);
    gpio_set_direction(LED_RED_PIN, GPIO_MODE_OUTPUT);

    gpio_set_intr_type(BUTTON_BACKWARD_PIN, GPIO_INTR_POSEDGE);
    gpio_set_intr_type(BUTTON_FORWARD_PIN, GPIO_INTR_POSEDGE);
    gpio_set_intr_type(BUTTON_PLAY_PAUSE_PIN, GPIO_INTR_POSEDGE);
    

    gpio_install_isr_service(ESP_INTR_FLAG_EDGE);
    gpio_isr_handler_add(BUTTON_FORWARD_PIN, ct_play_forward, 0);
    gpio_isr_handler_add(BUTTON_PLAY_PAUSE_PIN, ct_play_play_pause, 0);
    gpio_isr_handler_add(BUTTON_BACKWARD_PIN, ct_play_backward, 0);

    gpio_set_level(LED_GREEN_PIN,0);
    gpio_set_level(LED_RED_PIN,1);
}
void app_main(void)
{
    esp_err_t err;
    err = nvs_flash_init();
    if (err != ESP_OK) {
        ESP_LOGI(TAG, "nvs init error");
        return;
    }

    my_bt_set_gpio_pins();

    i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,                          // Only TX
        .sample_rate = 44100,
        .bits_per_sample = 16,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                   //2-channels
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,              // MAX98357A I2S Philips
        .dma_buf_count = 6,
        .dma_buf_len = 60,
        .intr_alloc_flags = 0,                                          //Default interrupt priority
        .tx_desc_auto_clear = true                                      //Auto clear tx descriptor on underflow
    };
    i2s_driver_install(0, &i2s_config, 0, NULL);

    i2s_pin_config_t pin_config = {
        .bck_io_num = 26,
        .ws_io_num = 22,
        .data_out_num = 25,
        .data_in_num = -1                                                       //Not used
    };

    i2s_set_pin(0, &pin_config);

    esp_bt_controller_config_t bt_controller  = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    err = esp_bt_controller_init(&bt_controller);
    err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);

    err= esp_bluedroid_init();
    err = esp_bluedroid_enable();

    err = esp_avrc_ct_register_callback(my_bt_avrc_cb);
    err = esp_avrc_ct_init();
   
    err = esp_a2d_register_callback(my_bt_a2d_cb);
    err = esp_a2d_sink_register_data_callback(my_bt_a2d_sink_data_cb);
    err = esp_a2d_sink_init();

    err = esp_bt_gap_register_callback(my_bt_gap_cb);
    err = esp_bt_dev_set_device_name(bt_device_name);
    esp_bt_io_cap_t io_cap=ESP_BT_IO_CAP_IO;
    err = esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &io_cap, sizeof(io_cap));
    err = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
   
   while(1) {
        if (avrc_conn) {
            vTaskDelay(5);
            if (ct_button_backward_press) {
                esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
                vTaskDelay(300/portTICK_PERIOD_MS);
                //esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE);
                ct_button_backward_press=false;
            } 
            if (ct_button_forward_press) {
                esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
                vTaskDelay(300/portTICK_PERIOD_MS);
                //esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE);
                ct_button_forward_press=false;
            } 
            if (ct_button_play_pause_press) {
                if (my_bt_speaker_state == ESP_AVRC_PLAYBACK_STOPPED || my_bt_speaker_state == ESP_AVRC_PLAYBACK_PAUSED) {
                    esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
                    my_bt_speaker_state = ESP_AVRC_PLAYBACK_PLAYING;
                } else  if (my_bt_speaker_state == ESP_AVRC_PLAYBACK_PLAYING) {
                    esp_avrc_ct_send_passthrough_cmd(1, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
                    my_bt_speaker_state = ESP_AVRC_PLAYBACK_PAUSED;
                }
                //esp_avrc_ct_send_metadata_cmd(1, ESP_AVRC_MD_ATTR_TITLE);
                vTaskDelay(300/portTICK_PERIOD_MS);
                ct_button_play_pause_press=false;
            } 
        } else {
            vTaskDelay(5);
        }
    }
}


2022年3月23日 星期三

STM32 使用HAL程式庫實作I2S介面語音錄音儲存與播放(STM32 I2S Audio record/store/play using HAL library)

 本實驗使用STM32F407VE I2S語音介面,以INMP441 MEMS microphone與MAX98357A DAC & Amplifier來實做一組錄音/儲存/播放語音設備。

一、使用元件

  1. STM32F407VE開發版
  2. INMP441
  3. MAX98357A
  4. 3W4Ω Speaker
  5. Micro SD card
  6. LM386

二、INMP441基本說明(摘錄自INMP441 datasheet)

  1. Digital I²S Interface with High-Precision 24-Bit Data
  2. 6 pins:
    SCK: Serial-Data Clock for I²S Interface
    SD: Serial-Data Output for I²S Interface
    WS: Serial Data-Word Select for I²S Interface
    L/R: Left/Right Channel Select. 本實驗只使用一個INMP441 Microphone, default is left channel
    VDD: Power, 1.8 V to 3.3 V.
    GND: Ground
  3. MSB-first
  4. Data Word Length: The output data word length is 24 bits per channel. The INMP441 must always have 64 clock cycles for every stereo data-word 。
  5. Data-Word Format:
    The default data format is I²S (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.

  6. 根據以上INMP441規格,本實驗ST32F407VE I2S2設為 Master Receive Mode, I2S Philips。雖然Data Word Length只有24Bits,但是補滿32bits傳送,所以STM32F407VE data and frame format設定為32 Bits Data in 32 Bit Frame。

三、MAX98357A基本說明(摘錄自MAX98357A datasheet)

  1. 16 or 32 bits Data length, MSB First
  2. No MCLK Required
  3. supporting 8kHz–96kHz sampling rates with 16/24/32-bit resolution for I2S/left justified data
  4. The MAX98357A accepts standard I2S data through DIN, BCLK, and LRCLK while the MAX98357B accepts left-justified data through the same inputs
  5. Pins:
    Vin: 3.3 or 5V
    GND: ground
    SD: 選左右聲道,內定為(left/2+right/2)
    Gain: 設定增益(9dB)
    DIN: Serial Data(即為STM32F407VE I2S的SD pin)
    BCLK: Serial-Data Clock for I²S Interface(同SCK)
    LRC: Serial Data-Word Select for I²S Interface(同WS)
  6. Input data format (Standard I2S 32 bit)


    STM32F407VE I2S3 Transmission Mode: Master Transmission Mode, Communication Standard: I2S Philips。

四、STM32F4xx I2S (摘錄RM0090 Reference manual)

  1. The I2S shares three common pins with the SPI
  2. I2S Philips standard: For this standard, the WS signal is used to indicate which channel is being transmitted. It is activated one CK clock cycle before the first bit (MSB) is available.
  3. MSB justified standard:  For this standard, the WS signal is generated at the same time as the first data bit, which is the MSB first.
  4.  LSB justified standard: This standard is similar to the MSB justified standard (no difference for the 16-bit and 32-bit full-accuracy frame formats).
  5. For all data formats and communication standards, the most significant bit is always sent first (MSB first).
  6. data width is 16bits
選擇I2S2連接INMP441, Master Receive Mode; Communication Standard: I2S Philips; data and frame format: 32 Bits Data in 32 Bit Frame。
選擇I2S3連接MAX93857A, Master Transmission Mode; Communication Standard: I2S Philips; data and frame format: 32 Bits Data in 32 Bit Frame。

五、MAX98357A輸出聲音過小問題:

    以上列方式實驗,錄音能取得正常音量,但由MAX98357A輸出時音量太小(如成果影片展示),將SD接HIGH(只輸出LEFT Channel),Gain 100KΩ接地(15dB)以獲得最大輸出。但音量仍不理想。在上篇實驗[ESP-IDF: ESP32 I2S介面語音錄音機(ESP32 I2S Audio Recorder using ESP-ADF)]使用相同的MAX98357A但能輸出較大的音量。
  1. 使用硬體放大:再將MAX98357A輸出接入LM386音頻放大器,再一次放大,可以獲得較大的音量(如成果影片展示)。
  2. 使用簡易軟體放大:((out_word)&0x80000000) |( ((out_word)<< 3)&0x7FFFFFFF );out_word為欲輸出的digital data先運算放大再輸出,可以獲得較大的音量(如成果影片展示)。

六、啟動與停止錄音按鈕

    實驗流程,按一次EXTI GPIO按鈕啟動錄音,儲存WAVE檔案至SD Card,再按一次停止錄音,接者播放儲存的WAVE檔案。按鈕採用硬體RC Debounce 線路。

七、WAVE Format

        實驗以WAVE format儲存在SD card上(有關STM32F4xx storage 可參閱另一篇文章[STM32微控制器(STM32F407VET6) SD-4bits、SD-SPI,FLASH等儲存設備管理]。
wave 檔案的header如下所示:
(圖片來源: Win38383838 - 自己的作品, CC BY-SA 4.0, 
https://commons.wikimedia.org/w/index.php?curid=46442654)

先寫入44bytes header,資依序寫入由INMP441 microphone擷取資料,最後修改總長度與data長度。INMP441為MSB格式(Big Endian),由於MAX98357A播放的資料亦為MSB,因此本實驗儲存在SD card上的檔案暫不轉換成Wave檔案little Endian格式,下列程式另提供轉換函式。
亦可存成AIFF檔案,AIFF為未壓縮PCM big endian格式,換成AIFF header直接輸出big endian資料,本實驗暫時未實作。

八、STM32CubeMX設定

  1. SDIO: 4 bits, SDIO_RX and SDIO_TX DMA enable


  2. EXTI GPIO button & and Red LED, Green LED: all pull down, enable EXTI line1 interrupt
  3. FatFS: 
  4. I2S2 for INMP441 MEMS Microphone:


    因為I2S 介面為16bits,  所以DMA data width只能選half word
  5. I2S3 for MAX98357A:

  6. RCC clock: High Speed Clock(HSE)-Crystal/Ceramic Resonate

九、成果展示



十、程式碼

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define MAX_DMA_ACCESS_COUNT	(40960)

#define DMA_READ_SIZE	(128)

#define BUFFER_COUNT	(4)

#define STATE_STOP				1
#define STATE_RECORDING			2
#define STATE_START_RECORDING	3
#define STATE_PLAYING			4
typedef union {
	uint32_t w;
	char b[4];
} _WORD;
typedef union  {
	uint16_t hw;
	char b[2];
} _HALF_WORD;

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
 I2S_HandleTypeDef hi2s2;
I2S_HandleTypeDef hi2s3;
DMA_HandleTypeDef hdma_spi2_rx;
DMA_HandleTypeDef hdma_spi3_tx;

RTC_HandleTypeDef hrtc;

SD_HandleTypeDef hsd;
DMA_HandleTypeDef hdma_sdio_rx;
DMA_HandleTypeDef hdma_sdio_tx;

/* USER CODE BEGIN PV */
uint16_t DMA_TxRx_SIZE = DMA_READ_SIZE*2;
static uint16_t rcvBuf[DMA_READ_SIZE*2*BUFFER_COUNT];
static uint32_t rCount=0, wCount=0;
static uint8_t audio_state = STATE_STOP;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SDIO_SD_Init(void);
static void MX_DMA_Init(void);
static void MX_I2S2_Init(void);
static void MX_RTC_Init(void);
static void MX_I2S3_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
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 convertEndian(char* sd_path, char *file_in, char *file_out) {
	WAVE_HEADER wave_header;
	FRESULT res;
	FIL fin, fout;
	char fn[256];
	UINT bw, br;
	uint16_t bitsSample;
	uint8_t readBytes;
	_WORD *w_data;
	_HALF_WORD *h_data;

	  //res = f_mount(&SDFatFS, SDPath, 0);
	sprintf(fn, "%s%s", sd_path, file_in);
	res = f_open(&fin, fn, FA_OPEN_EXISTING|FA_READ);
	sprintf(fn, "%s%s", sd_path, file_out);
	res = f_open(&fout, fn, FA_CREATE_ALWAYS|FA_WRITE);
	f_read(&fin, (uint8_t*)&wave_header, sizeof(wave_header), &br);

	  bitsSample= wave_header.bitsPerSample;
	  if (bitsSample == 32) {
		  w_data = (_WORD*)malloc(512);
	  } else if (bitsSample == 16){
		  h_data = (_HALF_WORD*)malloc(512);
	  } else {
		  return;
	  }


	  f_write(&fout, (uint8_t*)&wave_header, sizeof(wave_header), &bw);
	  for (int i=0; i < wave_header.data_size; i+=512) {
		  if (bitsSample == 32) {
			  f_read(&fin, (uint8_t*)w_data, 512, &br);
			  for (int i = 0; i < br/4; i++) {
				  w_data[i].w = w_data[i].b[0] << 24 | w_data[i].b[1] << 16 | w_data[i].b[2] << 8 | w_data[i].b[3];
			  }
			  f_write(&fout, (uint8_t*)(w_data), br, &bw);
		  }
		  else {
			  f_read(&fin, (uint8_t*)h_data, 512, &br);
			  for (int i = 0; i < br/2; i++) {
				  h_data[i].hw = h_data[i].b[0] << 8 | h_data[i].b[1];
			  }
			  f_write(&fout, (uint8_t*)(h_data), br, &bw);
		  }
	  }
	  f_close(&fout);
	  f_close(&fin);
}


uint8_t txCplt = 0;

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) {
	txCplt=1;
}
uint8_t rcvCplt = 0;
uint16_t* rpt, *wpt, *temppt;
void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s){
	rCount++;
	rpt = (rcvBuf)+(rCount%BUFFER_COUNT)*DMA_TxRx_SIZE;
	HAL_I2S_Receive_DMA(hi2s, rpt, DMA_READ_SIZE);
}

FRESULT fwrite_wav_header(FIL* file, uint16_t sampleRate, uint8_t bitsPerSample, uint8_t channels) {
	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*bitsPerSample*2/8;
	wave_header.bc =  bitsPerSample*2/8;
	wave_header.bitsPerSample = bitsPerSample; //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;
	return f_write(file, (uint8_t*)&wave_header, sizeof(wave_header), &bw);
}

void startRecord(char *filename) {
	UINT bw;
	UINT writeBytes;
	UINT skipCount=125;// skip 0.5 second
	FIL fp;
	FRESULT res;

	writeBytes = DMA_TxRx_SIZE*2;
	res = f_open(&fp, filename, FA_CREATE_ALWAYS|FA_WRITE);
	res = fwrite_wav_header(&fp, 16000, 32, 2);

	HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
	audio_state = STATE_RECORDING;
	rpt = rcvBuf;
	wpt = rpt;
	rCount=0; wCount=0;
	HAL_I2S_Receive_DMA(&hi2s2, rpt, DMA_READ_SIZE);

	while (1)
	{
		if (wCount < rCount ) {
			if (rCount > skipCount)
			{
				res = f_write(&fp, wpt, writeBytes, &bw);
			}
			 wCount++;
			 wpt = (rcvBuf)+(wCount%BUFFER_COUNT)*DMA_TxRx_SIZE;
		  }

		  if (audio_state == STATE_STOP || rCount > MAX_DMA_ACCESS_COUNT)
		  {
			  HAL_I2S_DMAStop(&hi2s2);
			  break;
		  }
	}
	uint32_t data_len = (wCount-1) * writeBytes;
	uint32_t total_len = data_len+36;
	f_lseek(&fp, 4);
	f_write(&fp, (uint8_t*)&total_len, 4, &bw);
	f_lseek(&fp, 40);
	f_write(&fp, (uint8_t*)&data_len, 4, &bw);
	f_close(&fp);
	HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
	audio_state = STATE_STOP;
}
void startPlay(char *filename) {

	FIL fp;
	WAVE_HEADER wave_header;
	UINT br;

	uint16_t *readpt, *writept, *temppt;
	FRESULT res;

	res = f_open(&fp, filename, FA_OPEN_EXISTING|FA_READ);

	res = f_read(&fp, (uint8_t*)&wave_header, sizeof(wave_header), &br);
	if (br != sizeof(wave_header)) {f_close(&fp);return; }

	audio_state = STATE_PLAYING;
	HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);

	readpt = rcvBuf;
	writept = (rcvBuf)+DMA_TxRx_SIZE;
	  txCplt=0;
	  f_read(&fp, (uint8_t*)readpt, DMA_TxRx_SIZE*2, &br);
	  temppt=writept;
	  writept=readpt;
	  readpt=temppt;
	  HAL_I2S_Transmit_DMA(&hi2s3, (uint16_t*)writept, DMA_READ_SIZE);
	  uint32_t *tu;
	  while (1) {
		  res = f_read(&fp, (uint8_t*)readpt, DMA_TxRx_SIZE*2, &br);
		  for (int i = 0; i < DMA_TxRx_SIZE; i+=2) {
			tu = (uint32_t*)&readpt[i];
			*tu = ((*tu)&0x80000000) | (((*tu) << 3)&0x7FFFFFFF);
		}

		  while(!txCplt) ;
		  txCplt=0;
		  temppt=writept;
		  writept=readpt;
		  readpt=temppt;

		  HAL_I2S_Transmit_DMA(&hi2s3, (uint16_t*)writept, DMA_READ_SIZE);
		  if (br < DMA_TxRx_SIZE*2) break;
	  }


	  HAL_I2S_DMAStop(&hi2s3);

	  f_close(&fp);
	  HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
	  audio_state = STATE_STOP;
}


void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if (GPIO_Pin == BUTTON_Pin) {
		switch (audio_state) {
		case STATE_STOP:
			audio_state = STATE_START_RECORDING;
			break;
		case STATE_RECORDING:
			audio_state = STATE_STOP;
			break;
		case STATE_PLAYING:
			break;
		case STATE_START_RECORDING:
			break;
		default:
			audio_state = STATE_STOP;
			break;
		}

	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

/* Configure the peripherals common clocks */
  PeriphCommonClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SDIO_SD_Init();
  MX_DMA_Init();
  MX_FATFS_Init();
  MX_I2S2_Init();
  MX_RTC_Init();
  MX_I2S3_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  FRESULT res;
  char filename[256];
  res = f_mount(&SDFatFS, SDPath, 0);

  uint16_t count;
   while (1)
  {
	    if (audio_state == STATE_START_RECORDING) {
	  		  HAL_Delay(1);
	  		  sprintf(filename, "%sr_%05d.wav", SDPath, count++);
	  		  startRecord(filename);

	  		  HAL_Delay(1000);
	  		  startPlay(filename);

	  	  }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

  f_mount(&SDFatFS, "", 0);

  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief Peripherals Common Clock Configuration
  * @retval None
  */
void PeriphCommonClock_Config(void)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Initializes the peripherals clock
  */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
  PeriphClkInitStruct.PLLI2S.PLLI2SN = 50;
  PeriphClkInitStruct.PLLI2S.PLLI2SR = 2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief I2S2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2S2_Init(void)
{

  /* USER CODE BEGIN I2S2_Init 0 */

  /* USER CODE END I2S2_Init 0 */

  /* USER CODE BEGIN I2S2_Init 1 */

  /* USER CODE END I2S2_Init 1 */
  hi2s2.Instance = SPI2;
  hi2s2.Init.Mode = I2S_MODE_MASTER_RX;
  hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;
  hi2s2.Init.DataFormat = I2S_DATAFORMAT_32B;
  hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
  hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_16K;
  hi2s2.Init.CPOL = I2S_CPOL_LOW;
  hi2s2.Init.ClockSource = I2S_CLOCK_PLL;
  hi2s2.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
  if (HAL_I2S_Init(&hi2s2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2S2_Init 2 */
  //hi2s2.Instance->CR1 = hi2s2.Instance->CR1 | SPI_CR1_LSBFIRST;
  /* USER CODE END I2S2_Init 2 */

}

/**
  * @brief I2S3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2S3_Init(void)
{

  /* USER CODE BEGIN I2S3_Init 0 */

  /* USER CODE END I2S3_Init 0 */

  /* USER CODE BEGIN I2S3_Init 1 */

  /* USER CODE END I2S3_Init 1 */
  hi2s3.Instance = SPI3;
  hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
  hi2s3.Init.Standard = I2S_STANDARD_PHILIPS;
  hi2s3.Init.DataFormat = I2S_DATAFORMAT_32B;
  hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
  hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_16K;
  hi2s3.Init.CPOL = I2S_CPOL_LOW;
  hi2s3.Init.ClockSource = I2S_CLOCK_PLL;
  hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
  if (HAL_I2S_Init(&hi2s3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2S3_Init 2 */
  //hi2s3.Instance->CR1 = hi2s3.Instance->CR1 | SPI_CR1_LSBFIRST;
  /* USER CODE END I2S3_Init 2 */

}

/**
  * @brief RTC Initialization Function
  * @param None
  * @retval None
  */
static void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */

  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef sDate = {0};

  /* USER CODE BEGIN RTC_Init 1 */

  /* USER CODE END RTC_Init 1 */

  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */
if (0) {
  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0;
  sTime.Minutes = 0;
  sTime.Seconds = 0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
  sDate.Month = RTC_MONTH_MARCH;
  sDate.Date = 6;
  sDate.Year = 22;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */
}
  /* USER CODE END RTC_Init 2 */

}

/**
  * @brief SDIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_SDIO_SD_Init(void)
{

  /* USER CODE BEGIN SDIO_Init 0 */

  /* USER CODE END SDIO_Init 0 */

  /* USER CODE BEGIN SDIO_Init 1 */

  /* USER CODE END SDIO_Init 1 */
  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;
  /* USER CODE BEGIN SDIO_Init 2 */

  /* USER CODE END SDIO_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn);
  /* DMA1_Stream5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
  /* DMA2_Stream3_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
  /* DMA2_Stream6_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, LED_RED_Pin|LED_GREEN_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : BUTTON_Pin */
  GPIO_InitStruct.Pin = BUTTON_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(BUTTON_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : LED_RED_Pin LED_GREEN_Pin */
  GPIO_InitStruct.Pin = LED_RED_Pin|LED_GREEN_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI1_IRQn);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */