(3)ラジオ局の表示

前回ラジオ局を16局から選択することが出来る様になりました。ただ、ラジオにディスプレイが無いので今どのラジオ局が選択されているか分かりません。そこで今回はラジオ局を表示しるディスプレイを追加します。

ディスプレイはこれを使います

ディスプレイとして、AQM1248A グラフィックLCD(for ESP-IDF) を使います。キャラクターディスプレイで良いのですが、手持ちにこのディスプレイしか無かったのでグラフィックディスプレイのこれを使用しました。

この機器はArduinoよPICで使用して経験の有る機器です。インターフェイスの”SPI” 部分のみ、ESP-IDF用に変更するれば、作成した関数はそのまま使用出来ます。

ハードウェア

ESP32との接続は下記の様に行っています。

 ESP-32  AQM1248A 
 GPIO16   CS
 EN   Reset
 GPIO15   RS
 GPIO18   SCLK
 GPIO23   SDI

ソフトウェア

ソフトはcompornets/my_boardフォルダーの下に新しくAQM1248フォルダーを作成し、そこに関連するソフトをまとめました。


- radiko-esp32-main_01/
      - components/
                   - radiko-esp32/
                              - CMakeLists.txt
                              - Kconfig.projbuild
                              - radiko.c
                              - radiko.h
                              - radiko_jp_rootcert.pem
                   - my_board/
                              - my_board_v1_0/
                                        - board.c
                                        - board.h
                                        - board_def.h
                                        - board_pins_config.c
                              - AQM1248/
                                        - AQM1248A.c
                                        - AQM1248A.h
                              - CMakeLists.txt
      - main/
                   - CMakeLists.txt
                   - Kconfig.projbuild
                   - play_radiko.c
      - CMakeLists.txt
      - partitions.csv
      - sdkconfig.defaults

ラジオ局表示の関数は、mainフォルダーの下の、play_radiko.c に記述しています。

play_radiko.c

/* Play M3U HTTP Living stream

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_common.h"
#include "http_stream.h"
#include "i2s_stream.h"
#include "aac_decoder.h"
#include "esp_tls.h"
#include "esp_http_client.h"
#include "radiko.h"
#include "esp_peripherals.h"
#include "periph_wifi.h"
#include "board.h"
#include "AQM1248A.h"
#include "audio_alc.h"

#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
#else
#define ESP_IDF_VERSION_VAL(major, minor, patch) 1
#endif

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
#include "esp_netif.h"
#else
#include "tcpip_adapter.h"
#endif

#include "driver/gpio.h"
#include <stdio.h>
#include <stdlib.h>

static const char *TAG = "HTTP_LIVINGSTREAM_EXAMPLE";

//#define AAC_STREAM_URI "http://open.ls.qingting.fm/live/274/64k.m3u8?format=aac"
#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_content 8192

audio_pipeline_handle_t pipeline;
audio_element_handle_t http_stream_reader, i2s_stream_writer, aac_decoder;

#define st_no1          35
#define st_no2          39
#define st_no4          34
#define st_no8          36

void init_port(void)
{
    gpio_config_t io_conf = {};
    io_conf.mode = GPIO_MODE_INPUT;	    			//set as input mode
    io_conf.pin_bit_mask = 0x27ULL << 34;			//bit mask of the pins GPIO34,35,36,39	
    io_conf.pull_up_en = 0;						    //enable pull-up mode
    gpio_config(&io_conf);
}

int get_staion_no()
{
  int a;

    a = 0;
    if(gpio_get_level(st_no1)) a += 1;
    if(gpio_get_level(st_no2)) a += 2;
    if(gpio_get_level(st_no4)) a += 4;
    if(gpio_get_level(st_no8)) a += 8;

    return a;
}

int _http_stream_event_handle(http_stream_event_msg_t *msg)
{
    if (msg->event_id == HTTP_STREAM_RESOLVE_ALL_TRACKS) {
        return ESP_OK;
    }

    if (msg->event_id == HTTP_STREAM_FINISH_TRACK) {
        return http_stream_next_track(msg->el);
    }
    if (msg->event_id == HTTP_STREAM_FINISH_PLAYLIST) {
        return http_stream_fetch_again(msg->el);
    }
    return ESP_OK;
}

void print_station_info(void)
{
	char c_buf[30];
	int a;
	
	LCD_CLS(0);
	a = get_staion_no();
	sprintf(c_buf, "%d:", a);
    strcat(c_buf,(stations + a)->id);
    a = strlen(c_buf);
	if(a > 10) {  c_buf[10] = 0; a = 10; }
	a = 10 - a;
	a *= 6;  
	LCD_Print_Str(a, 14, c_buf, 1, 2);
	
}

void app_main(void)
{
	int st_now;
	int flg_no;

    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES) 
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
    ESP_ERROR_CHECK(esp_netif_init());
#else
    tcpip_adapter_init();
#endif

	init_port();
	Init_LCD();

	LCD_CLS(0);
	LCD_Print_Str(30, 16, "Wait", 1, 2);

    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set(TAG, ESP_LOG_DEBUG);

    ESP_LOGI(TAG, "[2.0] Create audio pipeline for playback");
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline = audio_pipeline_init(&pipeline_cfg);

    ESP_LOGI(TAG, "[2.1] Create http stream to read data");
    http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
    http_cfg.event_handle = _http_stream_event_handle;
    http_cfg.type = AUDIO_STREAM_READER;
    http_cfg.enable_playlist_parser = true;
    http_stream_reader = http_stream_init(&http_cfg);

    ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_stream_writer = i2s_stream_init(&i2s_cfg);

    ESP_LOGI(TAG, "[2.3] Create aac decoder to decode aac file");
    aac_decoder_cfg_t aac_cfg = DEFAULT_AAC_DECODER_CONFIG();
    aac_decoder = aac_decoder_init(&aac_cfg);

    ESP_LOGI(TAG, "[2.4] Register all elements to audio pipeline");
    audio_pipeline_register(pipeline, http_stream_reader, "http");
    audio_pipeline_register(pipeline, aac_decoder,        "aac");
    audio_pipeline_register(pipeline, i2s_stream_writer,  "i2s");

    ESP_LOGI(TAG, "[2.5] Link it together http_stream-->aac_decoder-->i2s_stream-->[codec_chip]");
    const char *link_tag[3] = {"http", "aac", "i2s"};
    audio_pipeline_link(pipeline, &link_tag[0], 3);


    ESP_LOGI(TAG, "[ 3 ] Start and wait for Wi-Fi network");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);

    ESP_LOGI(TAG, "[3.1] Initialize keys on board");
    audio_board_key_init(set);

    periph_wifi_cfg_t wifi_cfg = 
    {
        .ssid = CONFIG_WIFI_SSID,
        .password = CONFIG_WIFI_PASSWORD,
    };

    esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
    esp_periph_start(set, wifi_handle);
    periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);

    auth();
    get_station_list();
	st_now = get_staion_no();
    
	generate_playlist_url(stations + st_now);
    ESP_LOGI(TAG, "[2.6] Set up  uri (http as http_stream, aac as aac decoder, and default output is i2s)");
    audio_element_set_uri(http_stream_reader, _playlist_url);

    ESP_LOGI(TAG, "[ 4 ] Set up  event listener");
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

    ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt);

    ESP_LOGI(TAG, "[4.2] Listening event from peripherals");
    audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

    ESP_LOGI(TAG, "[ 5 ] Start audio_pipeline");
    audio_pipeline_run(pipeline);

	print_station_info();

    while (1) 
    {
        audio_event_iface_msg_t msg;
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) 
        {
            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
            continue;
        }

        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
            && msg.source == (void *) aac_decoder
            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) 
        {
            audio_element_info_t music_info = {0};
            audio_element_getinfo(aac_decoder, &music_info);

            ESP_LOGI(TAG, "[ * ] Receive music info from aac decoder, sample_rates=%d, bits=%d, ch=%d",
                    music_info.sample_rates, music_info.bits, music_info.channels);

            audio_element_setinfo(i2s_stream_writer, &music_info);
            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
            continue;
        }

        /* restart stream when the first pipeline element (http_stream_reader in this case) receives stop event (caused by reading errors) */
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) http_stream_reader
            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int) msg.data == AEL_STATUS_ERROR_OPEN) 
        {
            ESP_LOGW(TAG, "[ * ] Restart stream");
            audio_pipeline_stop(pipeline);
            audio_pipeline_wait_for_stop(pipeline);
            audio_element_reset_state(aac_decoder);
            audio_element_reset_state(i2s_stream_writer);
            audio_pipeline_reset_ringbuffer(pipeline);
            audio_pipeline_reset_items_state(pipeline);
            audio_pipeline_run(pipeline);
            continue;
        }
        
        if (msg.source_type == PERIPH_ID_BUTTON)
        {
	        vTaskDelay(1000 / portTICK_PERIOD_MS);
	        flg_no = get_staion_no();
    	    if( flg_no != st_now)
    	    {
    	        ESP_LOGI(TAG, "Station No:%d",flg_no);
    	    	audio_pipeline_stop(pipeline);
    	        audio_pipeline_wait_for_stop(pipeline);
	
    	        st_now = flg_no;
    	        generate_playlist_url(stations + st_now);
    	        audio_element_set_uri(http_stream_reader, _playlist_url);
	
    	        ESP_LOGW(TAG, "[ * ] Restart stream");
    	        audio_element_reset_state(aac_decoder);
    	        audio_element_reset_state(i2s_stream_writer);
    	        audio_pipeline_reset_ringbuffer(pipeline);
    	        audio_pipeline_reset_items_state(pipeline);
    	        audio_pipeline_run(pipeline);
    	        print_station_info();
    	    }
        }
    }
    
    ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_terminate(pipeline);

    audio_pipeline_unregister(pipeline, http_stream_reader);
    audio_pipeline_unregister(pipeline, i2s_stream_writer);
    audio_pipeline_unregister(pipeline, aac_decoder);

    /* Terminate the pipeline before removing the listener */
    audio_pipeline_remove_listener(pipeline);

    /* Stop all peripherals before removing the listener */
    esp_periph_set_stop_all(set);
    audio_event_iface_remove_listener(esp_periph_set_get_event_iface(set), evt);

    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(pipeline);
    audio_element_deinit(http_stream_reader);
    audio_element_deinit(i2s_stream_writer);
    audio_element_deinit(aac_decoder);
    esp_periph_set_destroy(set);
}
  • 101行:void print_station_info(void)
    • ここで、局のデータを表示しています。
    • 今回は2倍角フォントを使用しているため、10文字のみの表示となります。
    • スイッチの値(0から15)とラジオ局のIDを表示します。

コンパイルと実行

  • ESP32を使用することを前提としています。
  • 先ずは、idf.py menuconfig を実行して初期設定を行います。
    • Topー>Audio HALー>Audio board custom audio board を選択
    • Example ConfigurationでWiFi関係の設定を行います。mainフォルダー下のKconfig.projbuildでWiFi関係の設定を行っていれば入力不要。
    • Radiko Configurationで認証キーを入力します。今回は、radiko-esp32フォルダー下のKconfig.projbuildで指定しているので入力不要。
  • この後、idf.py -p [USB Port] flash monitor を実行して下さい。コンパイルして実行出来ます。
  • 実行後しばらくしてラジオの再生が始まります。今回追加したディスプレイに局データが表示されます。
  • ロータリースイッチを回して下さい。ラジオ局が選択され局表示も更新されます。

下記は、7番のJ-Waveを選局したものです。

次回は

ラジオ局の表示が出来てかなり気に入っています。このプログラムを見るとRadikoの認証以外は通常のNet_Radioと操作を行っています。つまりRadiko以外のNet_Radioもプログラムの変更で聞くことが出来るということです。次はプログラムを変更して、Shoutcast のラジオを聞けるようにしたいと思います。

今回のプロジェクトをここに保存します。