鳥なき里のマイコン屋(139) ラズパイPico、内蔵ADCから外付けDACへ「スルー」

Joseph Halfmoon

前回は、ラズパイPicoにI2C接続の外付けのDAC、MCP4725を接続してノコギリ波など出力させてみました。今回はラズパイPicoの内蔵ADCで得たアナログ値をそのまま外付けDACに「転送」してアナログ波形を「再現」してみます。ぶっちゃけSTM32で昨日やった件のまねっこ。タイミングの取り方が異なるし、外付けDACは転送ネックだし、どうなることか。

※「鳥なき里のマイコン屋」投稿順Indexはこちら

(末尾に実験で使用したソース全文を添付しました。)

Raspberry Pi Picoに搭載のMCU、RP2040にはPWMはあれど、DACはないです。そこで前回、I2C接続の12bit DAC MCP4725(Microchip社製)を接続してみました。今のところI2Cバスのクロック400kHzで接続しているので、転送速度ネックでサンプリング周波数を上げることはできてません。それでも前回結果からは1kHz以下くらいの波形なら再現できそうな雰囲気(ちょっとサンプリング周波数の精度に疑問はあり~の。)

今回は、内蔵ADC(これまた12bitです)でアナログ入力波形を測定し、測定した値をそのまま外付けDACに送って波形を再現してみようと試みる次第。なお、ラズパイPicoのADCのSDKのAPIの使い方については以下にドキュメントがあります。

Raspberry Pi Pico SDK Documentation hardware_Adc

実験に使用したハードウエアの設定

前回の繰り返しになりますが、外付けDACは以下のように接続しています。

    • インタフェースは2個あるハードIPのうちi2c1使用
    • SDAはGP14,SCLはGP15にアサイン
    • MCP4725は秋月製のDIP化キット利用。小さいボード上にプルアップ抵抗、パスコンなど載っているのでお楽です。
    • I2Cアドレスは0x61に設定。A0端子をHighにしてあります。

内蔵ADCは、GP26を入力ピンとして使用しています。

タイミングはSDKの repeating_timer 使って設定していますが、設定値即サンプリング周波数というわけでもないです。周期の値はともかく、ほぼほぼ一定間隔で「回っている」感じ。

アナログ入力端子GP26に与える波形の生成と観察、MCP4725のアナログ出力端子の波形の観察などはDigilent Analog Discovery2利用です。

なお、ツールチェーンなどは母艦であるRaspberry Pi 4上で走らせており、PC上のVS Codeからラズパイ4にリモート接続して作業しています。

実験結果

末尾のソースをビルドして走らせた結果が以下です。黄色が入力波形、青が出力波形です。グラフ上は青の出力波形の電圧がY軸に見えています。黄色の入力波形はオフセット1V、振幅0.5V固定です。

その1,100Hzの正弦波を入力

ADDA_100Hz100Hzだと、パッと見、DAの「ガタガタ」は分からないです。特にDAの先にLPFなど接続していません。素の状態。

その2,500Hzの正弦波を入力

ADDA_500Hz今回のコードはDAへの「だいたい」転送ネック引っかからないように、成り行きでサンプリング周波数が決まっています。500Hzであると、階段が見えてますが、LPF通せばなんとかなる感じ?

その3,1kHzの正弦波を入力

ADDA_1kHz一応、1周期に6点くらい測定できている感じなのでまだナイキスト周波数を超えてはいないと思うのだけれど。ちょっとタイミングがずれてる?でもLPFかければ正弦波にはなりそう?

その4,2kHzの正弦波を入力

ADDA_2kHz流石に限界超えてるみたい。。。

今回は、前回からの「成り行き」でタイマAPIを使ってみました。しかしラズパイPicoのadcは、自分の中にサンプリング周期を作り出すためのタイマを持っています。そちらを使ったらもっと正確にタイミングがとれるかも。また、次回かね。でもDACのネックも解消しないと速くはならんでしょう。。。

鳥なき里のマイコン屋(139) ラズパイPico、外付けDAC、MCP4725接続 へ戻る

鳥なき里のマイコン屋(140) ラズパイPico、float計算でROMを読んでるよね へ進む

実験に使用したCのソース全文
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "hardware/i2c.h"
#include "hardware/timer.h"

// ON Board LED
#define LED_PIN (25)

// I2C defines
#define I2C_PORT i2c1
#define I2C_SDA 14
#define I2C_SCL 15
#define MCP4725_ADDR    (0x61)
// I2C clock [kHz]
#define I2C_SPEED       (400)
// timer interval [us]
#define TIMER_ITVL      (100)

volatile uint16_t    analogVal = 0; 

// ADC
#define ADC_IN  (26)

// Timer callback
bool repeating_timer_callback(struct repeating_timer *t) {
    uint8_t buf[2];
    analogVal = adc_read();
    buf[0] = (uint8_t)((analogVal & 0xF00) >> 8);
    buf[1] = (uint8_t)(analogVal & 0xFF);
    i2c_write_blocking(I2C_PORT, MCP4725_ADDR, buf, 2, false);
    return true;
}


int main()
{
    uint32_t counter = 0;

    stdio_init_all();
    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);

    // I2C Initialization.
    i2c_init(I2C_PORT, I2C_SPEED*1000);
    gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
    gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
    gpio_pull_up(I2C_SDA);
    gpio_pull_up(I2C_SCL);

    // Adc Initialization
    adc_init();
    adc_gpio_init(ADC_IN);
    adc_select_input(0);

    // Timer Initialization
    struct repeating_timer timer;
    add_repeating_timer_us(TIMER_ITVL, repeating_timer_callback, NULL, &timer);

    while (1) {
        printf("Counter: %d\n", ++counter);
        gpio_put(LED_PIN, 1);
        sleep_ms(1000);
        gpio_put(LED_PIN, 0);
        sleep_ms(1000);
    }
    bool cancelled = cancel_repeating_timer(&timer);
    bi_decl(bi_1pin_with_name(I2C_SDA, "I2C1 SDA->MCP4725"));
    bi_decl(bi_1pin_with_name(I2C_SCL, "I2C1 SCL->MCP4725"));
    bi_decl(bi_1pin_with_name(ADC_IN,  "ADC INPUT"));
    puts("End of Excution.");    
    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)

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

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

project(tstADC 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(tstADC tstADC.c )

pico_set_program_name(tstADC "tstADC")
pico_set_program_version(tstADC "0.1")

pico_enable_stdio_uart(tstADC 0)
pico_enable_stdio_usb(tstADC 1)

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

# Add any user requested libraries
target_link_libraries(tstADC
        hardware_adc
        hardware_i2c
        hardware_timer
        )

pico_add_extra_outputs(tstADC)