LT3080実験用DC電源プロジェクト(8)疑似SCPI?とマイナス電位発生タイミング

 電源をPCから制御できるようにする!というのが当初の目的の一つ。

https://funasover.blogspot.com/2026/01/blog-post.html

PCからのコマンドを受け取って、それで動作するようにしたい訳だが、コマンドはオリジナルでもよく、つまり”hoge 1.0"とシリアルで受け取ったら電圧を1.0Vにするとか。

このhogeを考えるのも一苦労。

そこでスキッピーを参考にして、コマンドを定めてやる。

*IDN?ってまず通信を確立できているか?GPIBなんかで接続テストする時に送る

これをArduinoで実装すると

void loop() {

  while (Serial.available()) {
    char c = Serial.read();

    if (c == '\r') continue;  // ★ CR を無視する
    if (c == '\n') {
      lineBuffer.trim();
      if (lineBuffer.length() > 0) {
        processSCPI(lineBuffer);
      }
      lineBuffer = "";
    } else {
      lineBuffer += c;
    }
  }
}

シリアルに何飛び込んだら、processSCPIという関数に飛ぶようにしておいて、

  // -------------------------
  // *IDN?
  // -------------------------
  if (cmd == "*IDN?") {
    Serial.println("Arduino, Power Supply System, 1.2");
    return;
  }

cmdの文字列を判定して、タスクをさせる。上の例だと、システムの名前を返すようにしている。

と同じように、

電圧を設定するVOLTというコマンドを用意。

  // -------------------------
// VOLT <value>
// CH1 の実電圧を指定する
// -------------------------
if (cmd.startsWith("VOLT")) {

    int spacePos = cmd.indexOf(' ');
    if (spacePos < 0) {
        Serial.println("ERR:SYNTAX");
        return;
    }

    // ★ 出したい実電圧
    float vout = cmd.substring(spacePos + 1).toFloat();

    // ★ CH1 のみ制御(index = 0)
    int ch_index = 0;

    // ★ 設定値を記録(実電圧)
    outputVoltage = vout;

    // ★ 出力が ON のときだけ DAC に反映
    if (outputEnabled) {

        // 実電圧 → DAC 指示値(2.975 倍補正)
        float v_dac = vout / 2.975f;

        // 0V 以下はクリップ
        if (v_dac < 0.0f) v_dac = 0.0f;

        DACVset(ch_index, v_dac);
    }

    return;
}

解説しておくと、

spacePos = cmd.indexOf(' ');

でスペースの位置を得ます。

float vout = cmd.substring(spacePos + 1).toFloat();

スペース以降の文字列を切り出してからフロートに変換

float v_dac = vout / 2.975f;

DACの指示値と実際の出力の関係のグラフをつくって2.975は決めています。

DAC後段に設けているオペアンプのゲインが3倍に設定しているため3付近の数です。

といった具合で、電圧の読み取り、電流制限値の設定、出力のON、OFFとったコマンドに対応できるようにしてみた。コードは、下の方に張っておきます。

これで、シリアルからコマンドを送ってやる事で、各種の設定ができるようになります。


あと、最初は気が付かず、なにもかも回路を壊しそうになったので。

LT3080を二つ使って、前段で電流制限、後段で電圧の出力を制御している構成です。

LT3080ですが、最低電流を0.5mAは流さないと電圧が出力されません。そこで、マイナス電源を用意して、そこに向かって定電流ダイオードで常に2mAの電流が流れるようにしている。


これはちゃんとゼロボルト付近まで電圧を制御できる点ではよいアプローチ。しかし注意が必要で、前段のLT3080に電流を流せる状態にない時には、設定電圧をゼロにしていても、電流を引き出す事がでいないので出力電圧がマイナスに引き込まれてマイナス電位を出力してしまいます。

これを回避するために、マイナス電源を生成しているNJU7660の電源電圧にP型MOSFETを使ってハイサイドのスイッチを入れています。


電源投入されると、マイコンでまず、前段のLT3080を電流が流せる状態にして、それからマイナス電位を発生させるようにしています。

これ措置していないと、電源入れたらマイナス電位が印加されるので、下手すると接続したものを壊してしまいます。


#include <SPI.h>

const int PIN_CS = PIN_PB2;
const int PIN_DAC_CS = PIN_PB1;
const int PIN_PMOS = PIN_PB0;

// DAC8555 用設定
SPISettings dacSetting(1000000, MSBFIRST, SPI_MODE1);
// ADS8341 用設定
SPISettings adcSetting(2000000, MSBFIRST, SPI_MODE0);

// 出力電圧ON/OFF 状態
bool outputEnabled =false;

//出力電圧設定値
float outputVoltage= 0.0;

//電流制限値を保持する変数
float currentLimit_mA = 1000.0f;   // デフォルト 1000mA

// ===== DAC8555 =====
void DACVset(uint8_t chan, float Vsetadc) {
  const float Vdacref = 3.300;
  uint16_t DataInt = (uint16_t)(Vsetadc / Vdacref * 65535.0);

  uint8_t cmd;
  switch (chan) {
    case 0: cmd = 0b00010000; break;  // A
    case 1: cmd = 0b00010010; break;  // B
    case 2: cmd = 0b00010100; break;  // C
    case 3: cmd = 0b00010110; break;  // D
    default: cmd = 0b00010000; break;
  }

  uint8_t msb = (DataInt >> 8) & 0xFF;
  uint8_t lsb = DataInt & 0xFF;

  digitalWrite(PIN_DAC_CS, LOW);
  SPI.beginTransaction(dacSetting);
  SPI.transfer(cmd);
  SPI.transfer(msb);
  SPI.transfer(lsb);
  SPI.endTransaction();
  digitalWrite(PIN_DAC_CS, HIGH);
}

// ===== ADC Utility =====
float adcToVoltage(uint16_t code) {
  const float Vref = 3.300f;
  return (float)code * (Vref / 65535.0f);
}

uint16_t readADS8341(uint8_t channel) {
  const uint8_t muxTable[4] = {
    0b10010111,  // CH0
    0b11010111,  // CH1
    0b10100111,  // CH2
    0b11100111   // CH3
  };

  channel &= 0x03;

  uint32_t sum = 0;
  SPI.beginTransaction(adcSetting);

  for (int i = 0; i < 100; i++) {
    uint8_t ctrl = muxTable[channel];

    digitalWrite(PIN_CS, LOW);
    SPI.transfer(ctrl);

    uint8_t b0 = SPI.transfer(0x00);
    uint8_t b1 = SPI.transfer(0x00);
    uint8_t b2 = SPI.transfer(0x00);
    digitalWrite(PIN_CS, HIGH);

    uint32_t raw = ((uint32_t)b0 << 16) | ((uint32_t)b1 << 8) | b2;
    uint16_t value = (raw >> 7) & 0xFFFF;

    sum += value;
  }

  SPI.endTransaction();
  return (uint16_t)(sum / 100);
}

// ===== UART Line Buffer =====
String lineBuffer = "";

void setup() {
  Serial.begin(38400);
  delay(300);

  Serial.println("READY");

  pinMode(PIN_CS, OUTPUT);
  digitalWrite(PIN_CS, HIGH);
  pinMode(PIN_DAC_CS, OUTPUT);
  digitalWrite(PIN_DAC_CS, HIGH);
  pinMode(PIN_PMOS, OUTPUT);
  digitalWrite(PIN_PMOS, HIGH);

  SPI.begin();
  while (Serial.available()) Serial.read();

  outputEnabled=false;
  outputVoltage= 0.0;
  DACVset(0, 0.0);  // ★ 出力は 0V に
  DACVset(1, 3.0);  // CCは電流を流せる状態に
  DACVset(2, 0.0);  // 未使用
  DACVset(3, 0.0);  // 未使用
  delay(10);
  digitalWrite(PIN_PMOS, LOW);  //PMOSを操作して、出力3.3Vをコンバーターに-3.3Vを生成
}

void loop() {

  while (Serial.available()) {
    char c = Serial.read();

    if (c == '\r') continue;  // ★ CR を無視する
    if (c == '\n') {
      lineBuffer.trim();
      if (lineBuffer.length() > 0) {
        processSCPI(lineBuffer);
      }
      lineBuffer = "";
    } else {
      lineBuffer += c;
    }
  }
}

// ===== SCPI Command Processor =====
void processSCPI(String cmd) {
  // ★ ECHO: を削除し、受信文字列そのまま返す
  //Serial.println(cmd);
 
  cmd.trim();
  if (cmd.length() == 0) return;   // ★ 空行は無視
  cmd.toUpperCase();

  // -------------------------
  // *IDN?
  // -------------------------
  if (cmd == "*IDN?") {
    Serial.println("Arduino, Power Supply System, 1.2");
    return;
  }

  // -------------------------
// VOLT <value>
// CH1 の実電圧を指定する
// -------------------------
if (cmd.startsWith("VOLT")) {

    int spacePos = cmd.indexOf(' ');
    if (spacePos < 0) {
        Serial.println("ERR:SYNTAX");
        return;
    }

    // ★ 出したい実電圧
    float vout = cmd.substring(spacePos + 1).toFloat();

    // ★ CH1 のみ制御(index = 0)
    int ch_index = 0;

    // ★ 設定値を記録(実電圧)
    outputVoltage = vout;

    // ★ 出力が ON のときだけ DAC に反映
    if (outputEnabled) {

        // 実電圧 → DAC 指示値(2.975 倍補正)
        float v_dac = vout / 2.975f;

        // 0V 以下はクリップ
        if (v_dac < 0.0f) v_dac = 0.0f;

        DACVset(ch_index, v_dac);
    }

    return;
}

// -------------------------
// OUTP ON
// CH1 の出力を ON にする
// -------------------------
if (cmd == "OUTP ON") {

    int ch_index = 0;  // CH1 固定

    outputEnabled = true;

    // 記録していた電圧に復帰
    DACVset(ch_index, outputVoltage);
    return;
}

// -------------------------
// OUTP OFF
// CH1 の出力を OFF にする
// -------------------------
if (cmd == "OUTP OFF") {

    int ch_index = 0;  // CH1 固定

   outputEnabled = false;
    // 出力をゼロに。
    DACVset(ch_index, 0.0);

    return;
}

// -------------------------
// MEAS:VOLT?
// CH1 の電圧を返す(実際に出力されている電圧を計測)
// -------------------------
if (cmd == "MEAS:VOLT?") {

    int ch = 0;  // CH1 固定

    uint16_t adc = readADS8341(ch);
    float v = adcToVoltage(adc);

    // ★ CH1 は分圧されているので補正
    v = v * 3.13f;   // CSV から求めた補正係数
    Serial.println(v, 6);
    return;
}
// -------------------------
// MEAS:CURR?
//1Ωの抵抗に実際に流れている電流出力されている電流値
// -------------------------

if (cmd == "MEAS:CURR?") {

    // CH2 の ADC を読む
    uint16_t adc = readADS8341(3);   // CH4 = index 3
    float v = adcToVoltage(adc);     // 差動アンプ出力電圧

    // ★ 電流[mA] に変換(CSV から求めた補正式)
    float current_mA = (0.355*v-0.006)*1000;//単位は[mA]

    // 負値は 0 にクリップ
    if (current_mA < 0.0f) current_mA = 0.0f;
    Serial.println(current_mA, 6);
    return;
}
// -------------------------
// CURR:LIM?
// -------------------------
if (cmd == "CURR:LIM?") {
    Serial.println(currentLimit_mA, 3);
    return;
}
// -------------------------
// CURR:LIM <value>
// -------------------------
if (cmd.startsWith("CURR:LIM")) {

    int spacePos = cmd.indexOf(' ');
    if (spacePos < 0) {
        Serial.println("ERR:SYNTAX");
        return;
    }

    // ★ 制限したい電流値
    float set_currentLimit_mA = cmd.substring(spacePos + 1).toFloat();

    // ★ CH2 のみ制御(index = 1)
    int ch_index = 1;

    // ★ 設定値を記録
    currentLimit_mA = set_currentLimit_mA;
    float dac_limit_voltage = 0.00344f * currentLimit_mA - 0.032f;
    DACVset(1, dac_limit_voltage);
    return;
}

  if (cmd.startsWith("PMOS ON")) {

    digitalWrite(PIN_PMOS, LOW);
    return;
  }
  if (cmd.startsWith("PMOS OFF")) {

    digitalWrite(PIN_PMOS, HIGH);
    return;
  }
 
  // -------------------------
  // Unknown command
  // -------------------------
  Serial.println("ERR:UNKNOWN COMMAND");
}


コメント

このブログの人気の投稿

【充電回路】TP4056(保護回路は電池側)

CH9329で日本語キーボード109で正しく表示する方法

Attiny85とAQM0802A(LCD)のI2C接続