CH32V307ベースのスマート電子負荷、組込みコンテスト作品をオープンソース化

CH32V307VCT6ベースのスマート電子負荷オープンソース、組込みコンテスト作品オープンソース、回路図・PCB・プログラムソース・作品レポート付き。

2023年組込みチップ&システム設計コンテスト応用部門、全国2位作品。

以前作ったHoltek版電子負荷をベースに半月で改造し、プログラムをCH32マイコンへ移植、RT-Threadシステムを導入してコードを最適化、急ピッチで制作。作りは大したことないので厳しいコメントはご遠慮ください

作品デモ動画:https://www.bilibili.com/video/BV1Zu4y1m7Zd/

HT32F52352ベースのスマート電子負荷オープンソース、Holtek Cup作品オープンソース:https://blog.zeruns.com/archives/784.html

本オープンソース作品は参考・学習用のみで、複製は推奨しません。LCSCオープンプラットフォームにはもっと優れた電子負荷オープンソース作品が多数あります!

本プロジェクトのLCSCオープンソースリンク:https://url.zeruns.com/Et4x4

電子・マイコン技術交流QQグループ:2169025065

WCH公式サイトで開発ボードサンプルを無料申請:https://url.zeruns.com/h9a99

電子負荷とは

電子負荷は、実際の負荷環境をシミュレートして電源や電子回路の性能をテストする電子機器です。大電力可変抵抗やニクロム線などの従来の無源負荷と比べ、パラメータ可変・使いやすさなどの利点があります。プロ開発者もアマチュアも電子負荷装置は必須です。

電源種類により交流電子負荷と直流電子負荷に分類され、機能面では定電流・定電圧・定抵抗・定電力の4タイプが一般的です。恒圧直流電源のテストでは電流供給能力が主なので、直流定電流電子負荷が最も使われます。制御方式では数値制御とアナログ制御があり、数値制御はパラメータ調整が直感的で機能拡張も簡単、自動テストも容易です。

プロジェクト概要

本作品はCH32V307VCT6(WCHマイコン)をメインコントローラとし、18650リチウム電池で駆動し携帯性を確保。

制御方式はマイコンDACで直流基準電圧を生成しオペアンプで電流/電圧検出増幅電圧と比較、オペアンプ出力でMOS-FETを制御し恒圧/恒電流を実現。

タッチディスプレイは陶晶馳2.8インチシリアルLCD、型番TJC3224T028_011R。

ヒートシンクは2Uサーバー用1356/1366ピンサイドフロー型。

プログラムはRT-Thread Studioで開発、回路設計はLCSC EDA。

最大入力100V/10A、最大200W。

実物写真

撮影枚数少ないのでデモ動画をご覧ください。





資料ダウンロード

以下リンクに回路図・LCSC EDAプロジェクト・PCB製造データ・ソースコード・シリアルLCDプロジェクト・チップマニュアル含む。

123パン不限速ダウンロード:https://www.123pan.com/ps/2Y9Djv-6NevH.html

百度网盘ダウンロード:https://pan.baidu.com/s/17YSlBZ6F1M18k7JGa7FlVA?pwd=buxx パスワード:buxx

部品購入先

部品はLCSC商城で購入することをおすすめ:https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

回路図

電源板



電源基板

コントロール基板

PCB

電源板

Top Layer

Bottom Layer

電源基板

Top Layer

Bottom Layer

コントロール基板

Top Layer

Bottom Layer

その他のオープンソースプロジェクトおすすめ- 三相電力計測器を作ってオープンソース化しました。家庭の電力使用状況を簡単に監視できます:https://blog.zeruns.com/archives/771.html

主要コード

main.cファイル

/********************************** (C) COPYRIGHT *******************************
 * File Name          : main.c
 * Author             : WCH
 * Version            : V1.0.0
 * Date               : 2021/06/06
 * Description        : Main program body.
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * SPDX-License-Identifier: Apache-2.0
 * https://blog.zeruns.com
 *******************************************************************************/
#include "ch32v30x.h"
#include<rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>
#include <rthw.h>
#include "drivers/pin.h"
#include <board.h>
#include <rtdbg.h>
#include <u8g2_port.h>
#include <qpid.h>

#include "USART.h"
#include "KEY.h"
#include "DAC.h"
#include "PWM.h"

/* グローバルtypedef */

/* グローバルdefine */
#define WDT_DEVICE_NAME    "wdt"    /* ウォッチドッグデバイス名 */
static rt_device_t wdg_dev; /* ウォッチドッグデバイスハンドル */
/* ADC基準電圧 */
#define VREF 3.3
/* 電源電圧 */
#define VCC 3.3
/* 補正キャリブレーションデータ */
#define V0_COMP 1.000   // 0.0325倍電圧レンジ
#define V1_COMP 1.000   // 0.0947倍電圧レンジ
#define V2_COMP 1.000   // 0.6175倍電圧レンジ
#define YIF1_COMP 1.00  // MOS管1電流サンプリング補償
#define YIF2_COMP 1.00  // MOS管2電流サンプリング補償
#define DAC1_COMP  1.00    // DAC1(VREF)出力補償係数
#define IREF2_COMP  1.00  // IREF2出力補償係数
#define DAC2_COMP   1.00   // DAC2(IREF1)出力補償
/* ADCサンプリング平均値計算回数 */
#define ADC_count 3
/* 一次ローパスフィルタフィルタ係数*/
#define dPower1 0.5
/* ピン番号、ドライバファイルdrv_gpio.cで確認 */
#define OLED_I2C_PIN_SCL    22  //PB6
#define OLED_I2C_PIN_SDA    23  //PB7
#define LED2                59  //PD11
#define LED1                60  //PD12
#define MCU_G0              62  //PD14
#define MCU_G1              63  //PD14

/* グローバル変数 */
u8g2_t u8g2;                    // u8g2の構造体変数
rt_uint16_t AD_Value[4];           //ADCサンプリングデータ

// モードページの列挙変数を定義
enum mode_type
{
    menu = 0, // メニュー
    CC,       // 定電流
    CV,       // 定電圧
    CR,       // 定抵抗
    CW        // 定電力
};

volatile uint8_t Eload_Out = 0;                 // 電子負荷出力ON/OFF状態
volatile uint8_t mode = menu;                   // 現在のモード
volatile uint8_t voltage_dw = 0;                // 電圧サンプリングレンジ、0は0.0325倍、2は0.6175倍、1は0.0947倍
volatile double YVF, YIF1, YIF2, YIF, VBAT;     // 現在の電圧電流
volatile double ISET, VSET, RSET, PSET;         // 電流、電圧、抵抗、電力設定値
volatile uint32_t YVF_SUM, YIF1_SUM, YIF2_SUM, VBAT_SUM;  // 電圧電流平均値計算用合計
volatile uint8_t AVG_count = 0;                 // 電流平均値計算累積カウント値
volatile uint8_t YVF_AVG_count = 0;             // 電圧平均値計算累積カウント値
volatile uint8_t VBAT_count = 0;                      // バッテリ電圧平均値計算累積カウント値
volatile uint8_t Key_ONOFF = 0;                 // 電子負荷ON/OFFボタン押下状態

static qpid_t qpid_CC;                          // PID制御データポインタ
static qpid_t qpid_CV;                          // PID制御データポインタ
static qpid_t qpid_CR;                          // PID制御データポインタ
static qpid_t qpid_CW;                          // PID制御データポインタ
static double I_SET, V_SET, R_SET, P_SET;

/* 関数宣言 */
void OLED_Init(void);
static int IWDG_Init();
static void thread1_sysLED_entry(void *parameter);
static void thread2_OLED_entry(void *parameter);
static void thread3_ADC_entry(void *parameter);
static void thread4_HMI_GetDate_entry(void *parameter);
static void thread5_ONOFF_entry(void *parameter);
static void thread7_HMI_Display_entry(void *parameter);
static void thread8_FAN_entry(void *parameter);
static void thread9_CWCR_entry(void *parameter);
static void thread10_BlueTooth_entry(void *parameter);
void CW_mode(void);
void CR_mode(void);
void key123(void);
void Thread_Init(void);
void SYS_Init(void);
void PID(void);

/*********************************************************************
 * @fn      main
 *
 * @brief   メインプログラム。
 *
 * @return  none
 */
int main(void)
{
    rt_kprintf("MCU: CH32V307\n");
    rt_kprintf("SysClk: %dHz\n", SystemCoreClock);
    SYS_Init();

    while (1)
    {
        // 過電流および過電力保護
        if (YIF > 10 | YVF * YIF > 300)
        {
            if (Eload_Out == 1)
            {
                Key_ONOFF = 1;
            }
        }
        rt_thread_mdelay(20);
    }
}

/* システム初期化 */
void SYS_Init(void)
{
    IWDG_Init();    // ウォッチドッグ初期化
    UART_Init();   // シリアル3初期化
    HMILCD_Send("page 0");   // 起動ページへ切替
    Dac_Init();     // DAC初期化
    PWM_Init();     // PWM初期化

    rt_pin_mode(MCU_G0, PIN_MODE_OUTPUT);   // IO口を出力モードに設定
    rt_pin_mode(MCU_G1, PIN_MODE_OUTPUT);
    rt_pin_write(MCU_G0, PIN_LOW);        // ローレベル出力
    rt_pin_write(MCU_G1, PIN_LOW);

    Thread_Init();   // スレッド作成
}

/* スレッド初期化 */
void Thread_Init(void)
{
    rt_thread_t tid = NULL; //スレッド制御ブロックポインタを定義
    /* スレッド作成 */
    tid = rt_thread_create("SYS_LED", thread1_sysLED_entry, NULL, 512, 30, 5);
    //"SYS_LED"という名前のスレッドを作成、エントリ関数はthread1_sysLED_entry、パラメータはNULL、スタックサイズは256バイト、優先度は30、タイムスライスは5tick
    if (tid != RT_NULL) // スレッド作成成功か判定
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread sysLED create success");
    }
    else
    {
        LOG_E("thread1 sysLED create failed...");
    }
    /*
     tid = rt_thread_create("OLED_Display", thread2_OLED_entry, NULL, 2048, 25, 30);
     if (tid != RT_NULL)
     {
     if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
     LOG_D("thread2 OLED create success");
     }
     else
     {
     LOG_E("thread2_OLED create failed...");
     }*/

    tid = rt_thread_create("ADC", thread3_ADC_entry, NULL, 1536, 18, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread3 ADC create success");
    }
    else
    {
        LOG_E("thread3 ADC create failed...");
    }

    tid = rt_thread_create("HMI_GetDate", thread4_HMI_GetDate_entry, NULL, 2048, 23, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread4 HMI_GetDate create success");
    }
    else
    {
        LOG_E("thread4 HMI_GetDate create failed...");
    }

    tid = rt_thread_create("ONOFF", thread5_ONOFF_entry, NULL, 1024, 15, 25);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread5 ONOFF create success");
    }
    else
    {
        LOG_E("thread5 ONOFF create failed...");
    }
    tid = rt_thread_create("KEY", thread6_KEY_entry, NULL, 512, 20, 20);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread6 KEY create success");
    }
    else
    {
        LOG_E("thread6 KEY create failed...");
    }

    tid = rt_thread_create("HMI_Display", thread7_HMI_Display_entry, NULL, 1024, 25, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread7 HMI_Display create success");
    }
    else
    {
        LOG_E("thread7 HMI_Display create failed...");
    }

    tid = rt_thread_create("FAN", thread8_FAN_entry, NULL, 512, 26, 15);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread8 FAN create success");
    }
    else
    {
        LOG_E("thread8 FAN create failed...");
    }

    tid = rt_thread_create("CWCR", thread9_CWCR_entry, NULL, 512, 19, 15);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread9 CWCR create success");
    }
    else
    {
        LOG_E("thread9 CWCR create failed...");
    }

    tid = rt_thread_create("BlueTooth", thread10_BlueTooth_entry, NULL, 2048, 23, 30);
    if (tid != RT_NULL)
    {
        if (rt_thread_startup(tid) == RT_EOK) // スレッド起動
            LOG_D("thread10 BlueTooth create success");
    }
    else
    {
        LOG_E("thread10 BlueTooth create failed...");
    }

}/* 一次ローパスフィルタ
 * 戻り値:iData 一次フィルタ後のサンプル値 */
double lowV1(double com1)
{
    static double iLastData1;    //前回値
    double iData1;               //今回計算値
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData1; //計算
    iLastData1 = iData1;                                     //今回データを保存
    return iData1;                                         //データを返す
}
double lowV2(double com1)
{
    static double iLastData2;    //前回値
    double iData1;               //今回計算値
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData2; //計算
    iLastData2 = iData1;                                     //今回データを保存
    return iData1;                                         //データを返す
}
u16 lowV3(u16 com1)
{
    static u16 iLastData3;    //前回値
    u16 iData1;               //今回計算値
    iData1 = (com1 * dPower1) + (1 - dPower1) * iLastData3; //計算
    iLastData3 = iData1;                                     //今回データを保存
    return iData1;                                         //データを返す
}
u16 lowV4(u16 com1)
{
    static u16 iLastData3;    //前回値
    u16 iData1;               //今回計算値
    iData1 = (com1 * 0.1) + (1 - 0.1) * iLastData3; //計算
    iLastData3 = iData1;                                     //今回データを保存
    return iData1;                                         //データを返す
}

static void idle_hook(void)
{
    /* アイドルスレッドのコールバック関数でウォッチドッグにエサをやる */
    rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
    //rt_kprintf("feed the dog!\n ");
}

static int IWDG_Init()
{
    rt_err_t ret = RT_EOK;
    rt_uint32_t timeout = 1; /* オーバーフロー時間、単位:秒 */
    /* デバイス名でウォッチドッグデバイスを検索し、デバイスハンドルを取得 */
    wdg_dev = rt_device_find(WDT_DEVICE_NAME);
    if (!wdg_dev)
    {
        rt_kprintf("find %s failed!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* デバイスを初期化 */
    rt_device_init(wdg_dev);
    /* ウォッチドッグのオーバーフロー時間を設定 */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
    if (ret != RT_EOK)
    {
        rt_kprintf("set %s timeout failed!\n", WDT_DEVICE_NAME);
        return RT_ERROR;
    }
    /* ウォッチドッグを起動 */
    ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, RT_NULL);
    if (ret != RT_EOK)
    {
        rt_kprintf("start %s failed!\n", WDT_DEVICE_NAME);
        return -RT_ERROR;
    }
    /* アイドルスレッドコールバック関数を設定 */
    rt_thread_idle_sethook(idle_hook);

    return ret;
}

/* スレッド1のエントリ関数、システム稼働状態LED点滅 */
static void thread1_sysLED_entry(void *parameter)
{
    /* LED1ピンを出力モードに設定 */
    rt_pin_mode(LED1, PIN_MODE_OUTPUT);
    /* デフォルトでローレベル */
    rt_pin_write(LED1, PIN_LOW);
    while (1)
    {
        /* スレッド1は低優先度で動作し、LED1を常に点滅させる */
        rt_pin_write(LED1, !rt_pin_read(LED1));
        rt_thread_mdelay(500);
    }
}

/* スレッド2のエントリ関数、OLED画面に情報を表示 */
static void thread2_OLED_entry(void *parameter)
{
    // 初期化
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_rtthread_hw_i2c, u8x8_gpio_and_delay_rtthread);
    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);

    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);
    while (1)
    {
        char String[26];
        u8g2_ClearBuffer(&u8g2);
        u8g2_SetFont(&u8g2, u8g2_font_wqy15_t_chinese3); // 中文字キャラクタセットを設定

        float V0 = AD_Value[0] * VREF / 4096.0;
        sprintf(String, "AD0:%d V:%d.%d%d%d", AD_Value[0], (uint8_t) V0, (uint16_t)(V0 * 10.0) % 10,
                (uint16_t)(V0 * 100.0) % 100 % 10, (uint16_t)(V0 * 1000.0) % 1000 % 100 % 10); // フォーマット文字列を文字列変数に出力
        u8g2_DrawStr(&u8g2, 0, 15, String);

        float V1 = AD_Value[1] * VREF / 4096.0;
        sprintf(String, "AD1:%d V:%d.%d%d%d", AD_Value[1], (uint8_t) V1, (uint16_t)(V1 * 10.0) % 10,
                (uint16_t)(V1 * 100.0) % 100 % 10, (uint16_t)(V1 * 1000.0) % 1000 % 100 % 10); // フォーマット文字列を文字列変数に出力
        u8g2_DrawStr(&u8g2, 0, 31, String);

        float V2 = AD_Value[2] * VREF / 4096.0;
        sprintf(String, "AD2:%d V:%d.%d%d%d", AD_Value[2], (uint8_t) V2, (uint16_t)(V2 * 10.0) % 10,
                (uint16_t)(V2 * 100.0) % 100 % 10, (uint16_t)(V2 * 1000.0) % 1000 % 100 % 10); // フォーマット文字列を文字列変数に出力
        u8g2_DrawStr(&u8g2, 0, 47, String);

        float V3 = AD_Value[3] * VREF / 4096.0;
        sprintf(String, "AD3:%d V:%d.%d%d%d", AD_Value[3], (uint8_t) V3, (uint16_t)(V3 * 10.0) % 10,
                (uint16_t)(V3 * 100.0) % 100 % 10, (uint16_t)(V3 * 1000.0) % 1000 % 100 % 10); // フォーマット文字列を文字列変数に出力
        u8g2_DrawStr(&u8g2, 0, 63, String);
        u8g2_SendBuffer(&u8g2); // バッファデータを送信
        rt_thread_mdelay(100);  // 100ミリ秒遅延
    }
}

/* スレッド3のエントリ関数、ADCデータ処理 */
static void thread3_ADC_entry(void *parameter)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); //GPIOAクロックとADCを有効化
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);      //DMA1クロックを有効化

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);       //ADCクロック分配設定、6分周(72MHz/6=12MHz)、ADCクロック周波数は14MHzを超えてはならない

    GPIO_InitTypeDef GPIO_InitStructure = { 0 };                //GPIO設定用構造体を定義
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;    //GPIOピンを設定
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //GPIOモードをアナログ入力に設定
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); //ADCレギュラーグループを設定、レギュラーグループのシーケンス1にチャネル0を書き込み、サンプリング時間239.5サイクル
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); //ADCレギュラーグループを設定、レギュラーグループのシーケンス2にチャネル1を書き込み、サンプリング時間239.5サイクル
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); //ADCレギュラーグループを設定、レギュラーグループのシーケンス3にチャネル2を書き込み、サンプリング時間239.5サイクル
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_239Cycles5); //ADCレギュラーグループを設定、レギュラーグループのシーケンス4にチャネル3を書き込み、サンプリング時間239.5サイクル

    ADC_InitTypeDef ADC_InitStructure = { 0 };
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  //ADCを独立モードに設定
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;        //マルチチャネルモードでスキャンモードを有効化
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  //連続変換モードを有効化
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //変換が外部トリガーで開始されないよう設定、ソフトウェアトリガーで開始
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADCデータを右揃えに設定
    ADC_InitStructure.ADC_NbrOfChannel = 4;                //レギュラー変換のADCチャネル数
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_Cmd(ADC1, ENABLE);      //ADC1を有効化

    ADC_ResetCalibration(ADC1); //ADC1キャリブレーションレジスタをリセット

    while (ADC_GetResetCalibrationStatus(ADC1))
        ; //リセットキャリブレーション終了を待つ

    ADC_StartCalibration(ADC1); //ADキャリブレーションを開始

    while (ADC_GetCalibrationStatus(ADC1))
        ;      //キャリブレーション終了を待つ

    DMA_DeInit(DMA1_Channel1); //DMAコントローラをリセット
    DMA_InitTypeDef DMA_InitStructure;                      //DMA設定用構造体を定義
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) &ADC1->RDATAR; //ペリフェラルアドレスをADCデータレジスタアドレスに設定
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32) AD_Value;          //メモリアドレスをADC値読み取りアドレスに設定
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;              //データソースをペリフェラルに設定、DMA転送方式をペリフェラルからメモリに設定
    DMA_InitStructure.DMA_BufferSize = 4;                           //DMAデータバッファサイズを設定
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                           //DMAペリフェラルインクリメントモードを無効化
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         //DMAメモリインクリメントモードを有効化
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //ペリフェラルデータサイズをハーフワード(2バイト)に設定
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         //メモリデータサイズをハーフワード(2バイト)に設定
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;     //DMAモードを循環転送モードに設定
    DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA転送チャネル優先度を高に設定、単一DMAチャネル使用時は優先度設定は影響しない
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;        //DMA転送方式がペリフェラルからメモリのため、メモリ間転送方式を無効化
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);        //DMA1チャネル1を初期化、ADC1のハードウェアトリガーはDMA1チャネル1に接続されているため、DMA1チャネル1を使用する必要がある

    DMA_Cmd(DMA1_Channel1, ENABLE); //DMA1チャネル1を起動
    ADC_DMACmd(ADC1, ENABLE);       // ADC DMA リクエストを有効化
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);       // 外部トリガーを使用していないため、ソフトウェアでADC変換をトリガー

    qpid_init(&qpid_CC);            // PID制御データを初期化
    qpid_set_lmt(&qpid_CC, 0, 10);  // PID制限値を設定
    qpid_set_ratio(&qpid_CC, 1, 0.001, 0.1);    // 制御比率係数を設定

    qpid_init(&qpid_CV);                // PID制御データを初期化
    qpid_set_lmt(&qpid_CV, 31.0, 100);  // PID制限値を設定
    qpid_set_ratio(&qpid_CV, 0.35, 0.005, 0.001);    // 制御比率係数を設定

   qpid_init(&qpid_CW);                // PID制御データを初期化
    qpid_set_lmt(&qpid_CW, 0.1, 200);  // PID制限値を設定
    qpid_set_ratio(&qpid_CW, 0.5, 0.003, 0.001);    // 制御比率係数を設定

    qpid_init(&qpid_CR);                // PID制御データを初期化
    qpid_set_lmt(&qpid_CR, 0.1, 1000);  // PID制限値を設定
    qpid_set_ratio(&qpid_CR, 0.35, 0.003, 0.001);    // 制御比率係数を設定

    while (1)
    {
        /*rt_kprintf("AD0:%d V:%f\n", AD_Value[0], AD_Value[0] / 4095.0 * VREF);
         rt_kprintf("AD1:%d V:%4.3f\n", AD_Value[1], (float) AD_Value[1] / 4095.0 * VREF);
         rt_kprintf("AD2:%d V:%4.3f\n", AD_Value[2], AD_Value[2] / 4095.0 * VREF);*/

        if (voltage_dw == 0) // 電圧サンプリングレンジが0.0325倍の場合
        {
            if (YVF_AVG_count < ADC_count) // 15未満のときサンプリング値を累積
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0325 * V0_COMP;    // 電圧値を計算
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }
            if (YVF <= 31.0) // 電圧が31V未満のとき、レンジを切り替え
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH); // 電圧サンプリングレンジ0.0947倍
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // DAC1出力値を設定し、定電圧を制御
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // PID制限値を設定
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // 制御比率係数を設定
            }
        }
        else if (voltage_dw == 1) // 電圧レンジが0.0947倍の場合
        {
            if (YVF_AVG_count < ADC_count)
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.0947 * V1_COMP;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }

            if (YVF >= 33.5)  // 電圧が33.5V以上で0.0325倍レンジに切り替え
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_LOW);
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // DAC1出力値を設定し、定電圧を制御
                }
                voltage_dw = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 31.0, 100); // PID制限値を設定
                qpid_set_ratio(&qpid_CV, 0.5, 0.005, 0.0005);    // 制御比率係数を設定
            }
            else if (YVF <= 4.6) // 電圧が4.6V未満のときレンジを切り替え
            {
                rt_pin_write(MCU_G0, PIN_HIGH);
                rt_pin_write(MCU_G1, PIN_LOW);
                if (Eload_Out == 1)
                {
                    uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                    if (vset_pwm > 4065)
                        vset_pwm = 4095;
                    DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // DAC1出力値を設定し、定電圧を制御
                }
                voltage_dw = 2;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 0.01, 5.1); // PID制限値を設定
                qpid_set_ratio(&qpid_CV, 0.26, 0.0025, 0.0005);    // 制御比率係数を設定
            }
        }
        else if (voltage_dw == 2) // 電圧レンジが0.6175倍の場合
        {
            if (YVF_AVG_count < ADC_count)
            {
                YVF_SUM += AD_Value[0];
                YVF_AVG_count++;
            }
            if (YVF_AVG_count == ADC_count)
            {
                YVF = YVF_SUM / YVF_AVG_count * VREF / 4096.0 / 0.6175 * V2_COMP;
                if (YVF < 0.15)
                    YVF = 0;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
            }

            if (YVF >= 5.1) // 電圧が5.1V以上のとき、レンジを切り替え
            {
                rt_pin_write(MCU_G0, PIN_LOW);
                rt_pin_write(MCU_G1, PIN_HIGH);
                if (Eload_Out == 1)
                {
                    DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // DAC1出力値を設定し、定電圧を制御
                }
                voltage_dw = 1;
                YVF_AVG_count = 0;
                YVF_SUM = 0;
                qpid_set_lmt(&qpid_CV, 4.6, 33.5); // PID制限値を設定
                qpid_set_ratio(&qpid_CV, 0.38, 0.0035, 0.0005);    // 制御比率係数を設定
            }
        }

        if (AVG_count < ADC_count)
        {
            YIF1_SUM += AD_Value[1]; // MOS管1電流を累積
            YIF2_SUM += AD_Value[2]; // MOS管2電流を累積
            AVG_count++;
        }
        if (AVG_count == ADC_count)
        {
            YIF1 = YIF1_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF1_COMP - 0.007;
            YIF2 = YIF2_SUM / AVG_count * VREF / 4096.0 / 50 / 0.01 * YIF2_COMP - 0.007;
            YIF = YIF1 + YIF2;
            if (YIF < 0.008)
                YIF = 0;
            AVG_count = 0;
            YIF1_SUM = 0;
            YIF2_SUM = 0;
            PID();
        }

        if (VBAT_count < 5)
        {
            VBAT_SUM += lowV4(AD_Value[3]); // バッテリ電圧サンプリング値を累積
            VBAT_count++;
        }
        if (VBAT_count == 5)
        {
            VBAT = VBAT_SUM / VBAT_count * VREF / 4096.0 / 0.3535;
            VBAT_count = 0;
            VBAT_SUM = 0;
        }
        rt_thread_mdelay(5);
    }
}

// https://blog.zeruns.com/* スレッド4のエントリ関数。シリアル画面からのデータを処理 */
static void thread4_HMI_GetDate_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // 画面に表示される電流設定値をクリア
    HMILCD_Send("CV.x0.val=0"); // 画面に表示される電圧設定値をクリア
    HMILCD_Send("CR.x0.val=0"); // 画面に表示される抵抗設定値をクリア
    HMILCD_Send("CW.x0.val=0"); // 画面に表示される電力設定値をクリア
    while (1)
    {
        if (Serial3_RxFlag == 1)
        {
            if (Serial3_RxPacket[0] == 0x01) // 現在はメインメニューページ
            {
                if (Serial3_RxPacket[1] == 0x10) // 定電流ボタンが押された
                {
                    HMILCD_Send("page CC"); // 定電流モードページへ切り替え
                    mode = CC;                    // 現在のモードを定電流モードに設定
                }
                else if (Serial3_RxPacket[1] == 0x11) // 定電圧ボタンが押された
                {
                    HMILCD_Send("page CV"); // 定電圧ページへ切り替え
                    mode = CV;                    // 現在のモードを定電圧モードに設定
                }
                else if (Serial3_RxPacket[1] == 0x12) // 定抵抗ボタンが押された
                {
                    HMILCD_Send("page CR"); // 定抵抗ページへ切り替え
                    mode = CR;                    // 現在のモードを定抵抗モードに設定
                }
                else if (Serial3_RxPacket[1] == 0x13) // 定電力ボタンが押された
                {
                    HMILCD_Send("page CW"); // 定電力ページへ切り替え
                    mode = CW;                    // 現在のモードを定電力モードに設定
                }
            }
            else if (Serial3_RxPacket[0] == 0x02) // 現在は定電流モードページ
            {
                if (Serial3_RxPacket[1] == 0x10) // メニューボタンが押された
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // 画面右上のタイトル欄にOFFを表示
                    HMILCD_Send("CC.b1.txt=\"開啓\""); // 画面右下のボタンに「開啓」を表示
                    Eload_Out = 0;                           // 負荷出力状態をオフに設定
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力で定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力で定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");          // メニューページへ切り替え
                    mode = menu;
                }
                else if (Serial3_RxPacket[1] == 0x11) // 開啓ボタンが押され、現在の負荷出力状態がオフ
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x03) // 現在は定電圧モードページ
            {
                if (Serial3_RxPacket[1] == 0x10) // メニューボタンが押された
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"開啓\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力で定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力で定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // 開啓ボタンが押され、現在の負荷出力状態がオフ
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x04) // 現在は定抵抗モードページ
            {
                if (Serial3_RxPacket[1] == 0x10) // メニューボタンが押された
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"開啓\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力で定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力で定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // 開啓ボタンが押され、現在の負荷出力状態がオフ
                {
                    Key_ONOFF = 1;
                }
            }
            else if (Serial3_RxPacket[0] == 0x05) // 現在は定電力モードページ
            {
                if (Serial3_RxPacket[1] == 0x10) // メニューボタンが押された
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"開啓\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力で定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力で定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                if (Serial3_RxPacket[1] == 0x11) // 開啓ボタンが押され、現在の負荷出力状態がオフ
                {
                    Key_ONOFF = 1;
                }
            }

            else if (Serial3_RxPacket[0] == 0xAA) // 現在は数字キーボードページ
            {
                char *temp = Serial3_RxPacket;
                temp++;                      // アドレスを1増加
                uint16_t temp2 = atoi(temp); // 文字列を整数に変換
                if (mode == CC)
                {
                    if (temp2 > 1000)
                        temp2 = 1000;
                    ISET = temp2 / 100.0;
                    HMILCD_Send("CC.x0.val=%d", temp2);

                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                        if (ISET <= 2.5)
                        {
                            // DAC2出力値を設定し定電流を制御、+0.5は四捨五入用
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // IREF2を設定
                        }
                        else
                        {
                            // DAC2出力値を設定し定電流を制御、+0.5は四捨五入用
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // IREF2を設定
                        }

                    }

                }
                else if (mode == CV)
                {
                    VSET = temp2 / 100.0;
                    HMILCD_Send("CV.x0.val=%d", temp2);
                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // IREF1を設定
                        PWM_SetCCR4(50000);  // IREF2を設定
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // DAC1出力値を設定し定電圧を制御
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // DAC1出力値を設定し定電圧を制御
                        }
                        else if (voltage_dw == 2)
                        {
                            uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                            if (vset_pwm > 4065)
                                vset_pwm = 4095;
                            DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // DAC1出力値を設定し定電圧を制御
                        }
                    }
                }
                else if (mode == CR)
                {
                    RSET = temp2 / 100.0;
                    HMILCD_Send("CR.x0.val=%d", temp2);
                    //CR_mode();
                }
                else if (mode == CW)
                {
                    PSET = temp2 / 100.0;
                    HMILCD_Send("CW.x0.val=%d", temp2);
                    //CW_mode();
                }
            }
            Serial3_RxFlag = 0;
        }
        key123();
        rt_thread_mdelay(35);
    }

}/*恒功率モード*/
void CW_mode(void)
{
    double Ptemp = PSET / YVF;
    if (Ptemp > 10)
        Ptemp = 10;
    if (Eload_Out == 1)
    {
        // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入のため
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // IREF2を設定
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
        PWM_SetCCR4(0);  // IREF2を設定
    }
}

/*恒抵抗モード*/
void CR_mode(void)
{
    double Rtemp = YVF / RSET;
    if (Rtemp > 10)
        Rtemp = 10;
    if (Eload_Out == 1)
    {
        // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入のため
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // IREF2を設定
    }
    else
    {
        DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
        DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
        PWM_SetCCR4(0);  // IREF2を設定
    }
}

/* PID制御  */
void PID(void)
{
    double Ptemp, Rtemp;

    if (mode == CC && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CC, ISET);   // PID目標値を設定
        I_SET = qpid_cal_pos(&qpid_CC, YIF);    // PID計算
        if (ISET <= 2.5)
        {
            // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入のため
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        }
        else
        {
            // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入のため
            DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(I_SET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
            PWM_SetCCR4((uint16_t)(I_SET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // IREF2を設定
        }
    }
    if (mode == CV && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CV, VSET);           // PID目標値を設定
        V_SET = qpid_cal_pos(&qpid_CV, YVF);    // PID計算
        if (voltage_dw == 0)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
            // DAC1出力値を設定し、定電圧を制御
        }
        else if (voltage_dw == 1)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
            // DAC1出力値を設定し、定電圧を制御
        }
        else if (voltage_dw == 2)
        {
            DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(V_SET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
            // DAC1出力値を設定し、定電圧を制御
        }
    }
    if (mode == CW && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CW, PSET);   // PID目標値を設定
        P_SET = qpid_cal_pos(&qpid_CW, YIF * YVF);    // PID計算
        Ptemp = P_SET / YVF;
        if (Ptemp > 10)
            Ptemp = 10;
        // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入のため
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Ptemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Ptemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // IREF2を設定
    }
    if (mode == CR && Eload_Out == 1)
    {
        qpid_set_dst(&qpid_CR, RSET);   // PID目標値を設定
        R_SET = qpid_cal_pos(&qpid_CR, YVF / YIF);    // PID計算
        Rtemp = YVF / R_SET;
        if (Rtemp > 10)
            Rtemp = 10;
        // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入のため
        DAC_SetChannel2Data(DAC_Align_12b_R, (uint16_t)(Rtemp / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
        PWM_SetCCR4((uint16_t)(Rtemp / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // IREF2を設定
    }

}

/* キー処理関数 */
void key123(void)
{
    if (key[2] == 1)    // キー2、切替キー
    {
        if (mode == menu)
        {
            HMILCD_Send("page CC"); // 定電流モードページに切替
            mode = CC;              // 現在のモードを定電流モードに設定
        }
        else if (mode == CC)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CC.t1.txt=\"OFF\"");
                HMILCD_Send("CC.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                PWM_SetCCR4(0);  // IREF2を設定
            }
            HMILCD_Send("page CV"); // 定電圧モードページに切替
            mode = CV;              // 現在のモードを定電圧モードに設定
        }
        else if (mode == CV)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CV.t1.txt=\"OFF\"");
                HMILCD_Send("CV.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                PWM_SetCCR4(0);  // IREF2を設定
            }
            HMILCD_Send("page CR"); // 定抵抗モードページに切替
            mode = CR;              // 現在のモードを定抵抗モードに設定
        }
        else if (mode == CR)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CR.t1.txt=\"OFF\"");
                HMILCD_Send("CR.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                PWM_SetCCR4(0);  // IREF2を設定
            }
            HMILCD_Send("page CW"); // 定電力モードページに切替
            mode = CW;              // 現在のモードを定電力モードに設定
        }
        else if (mode == CW)
        {
            if (Eload_Out == 1)
            {
                HMILCD_Send("CW.t1.txt=\"OFF\"");
                HMILCD_Send("CW.b1.txt=\"开启\"");
                Eload_Out = 0;
                DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                PWM_SetCCR4(0);  // IREF2を設定
            }
            HMILCD_Send("page menu"); // メニューページに切替
            mode = menu;              // 現在のモードをメニューモードに設定
        }
        key[2] = 0;
    }
    if (key[3] == 1)    // キー3、メニューキー
    {
        if (Eload_Out == 1)
            Key_ONOFF = 1;
        rt_thread_mdelay(15);
        HMILCD_Send("page menu"); // メニューページに切替
        mode = menu;              // 現在のモードをメニューモードに設定
        key[3] = 0;
    }
}/* スレッド5のエントリ関数、電子負荷のON/OFFボタン処理 */
static void thread5_ONOFF_entry(void *parameter)
{
    /* LED2ピンを出力モードに設定 */
    rt_pin_mode(LED2, PIN_MODE_OUTPUT);
    /* デフォルトでハイレベル */
    rt_pin_write(LED2, PIN_HIGH);
    while (1)
    {
        if (Key_ONOFF == 1 | key[1] == 1) // 起動ボタンが押された
        {
            if (mode == CC) // 定電流モード
            {
                if (Eload_Out == 0) // 現在の負荷出力状態がOFFの場合
                {
                    HMILCD_Send("CC.t1.txt=\"ON\"");   // 画面右上のタイトルボックスにONを表示
                    HMILCD_Send("CC.b1.txt=\"关闭\"");    // 画面右下のボタンに「关闭」と表示
                    Eload_Out = 1;                           // 負荷出力状態をONに設定
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    if (ISET <= 2.5)
                    {
                        // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入用
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4(0);  // IREF2を設定
                    }
                    else
                    {
                        // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入用
                        DAC_SetChannel2Data(DAC_Align_12b_R,
                                (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                        PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5));  // IREF2を設定
                    }
                }
                else if (Eload_Out == 1) // 現在の負荷出力状態がONの場合
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");
                    HMILCD_Send("CC.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1をハイレベル出力、定電圧をOFF
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2をローレベル出力、定電流をOFF
                    PWM_SetCCR4(0);  // IREF2を設定
                }
            }
            else if (mode == CV) // 定電圧モード
            {
                if (Eload_Out == 0) // 現在の負荷出力状態がOFFの場合
                {
                    HMILCD_Send("CV.t1.txt=\"ON\"");
                    HMILCD_Send("CV.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // IREF1を設定
                    PWM_SetCCR4(50000);  // IREF2を設定
                    if (voltage_dw == 0)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5));
                        // DAC1出力値を設定し、定電圧を制御
                    }
                    else if (voltage_dw == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5));
                        // DAC1出力値を設定し、定電圧を制御
                    }
                    else if (voltage_dw == 2)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5));
                        // DAC1出力値を設定し、定電圧を制御
                    }
                }
                else if (Eload_Out == 1) // 現在の負荷出力状態がONの場合
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1をハイレベル出力、定電圧をOFF
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2をローレベル出力、定電流をOFF
                    PWM_SetCCR4(0);  // IREF2を設定
                }
            }
            else if (mode == CR) // 定抵抗モード
            {
                if (Eload_Out == 0) // 現在の負荷出力状態がOFFの場合
                {
                    HMILCD_Send("CR.t1.txt=\"ON\"");
                    HMILCD_Send("CR.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    CR_mode();
                }
                else if (Eload_Out == 1) // 現在の負荷出力状態がONの場合
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1をハイレベル出力、定電圧をOFF
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2をローレベル出力、定電流をOFF
                    PWM_SetCCR4(0);  // IREF2を設定
                }
            }
            else if (mode == CW) // 定電力モード
            {
                if (Eload_Out == 0) // 現在の負荷出力状態がOFFの場合
                {
                    HMILCD_Send("CW.t1.txt=\"ON\"");
                    HMILCD_Send("CW.b1.txt=\"关闭\"");
                    Eload_Out = 1;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                    CW_mode();
                }
                else if (Eload_Out == 1) // 現在の負荷出力状態がONの場合
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1をハイレベル出力、定電圧をOFF
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2をローレベル出力、定電流をOFF
                    PWM_SetCCR4(0);  // IREF2を設定
                }
            }
            Key_ONOFF = 0;
            key[1] = 0;
        }

        if (Eload_Out == 0)
        {
            rt_pin_write(LED2, SET);
        }
        else if (Eload_Out == 1)
        {
            rt_pin_write(LED2, RESET);
        }
        rt_thread_mdelay(35);
    }

}

/* スレッド7のエントリ関数、シリアル画面へのパラメータ表示 */
static void thread7_HMI_Display_entry(void *parameter)
{
    while (1)
    {
        if (mode != menu)
        {
            double V = lowV1(YVF);
            double I = lowV2(YIF);
            HMILCD_Send("x1.val=%d", (uint16_t)(V * 100));       // 電圧を表示
            HMILCD_Send("x2.val=%d", (uint16_t)(I * 1000));      // 電流を表示
            HMILCD_Send("x3.val=%d", (uint32_t)(I * V * 100)); // 電力を表示
            HMILCD_Send("x4.val=%d", (uint32_t)(V / I * 100)); // 抵抗を表示
            HMILCD_Send("x5.val=%d", (uint32_t)(VBAT * 100));     // 抵抗を表示
        }
        rt_thread_mdelay(50);
    }
}

/* スレッド8のエントリ関数、冷却ファン制御 */
static void thread8_FAN_entry(void *parameter)
{
    while (1)
    {
        uint16_t P = (uint16_t)(YIF * YVF + 0.5);
        if (P >= 13) // 電力が13W以上の場合ファンを起動
        {
            FAN_PWM_ON();
            if (P < 20)
            {
                FAN_PWM_SetCCR(20); // ファン制御デューティ比20%
            }
            else if (P >= 20 && P < 25)
            {
                FAN_PWM_SetCCR(30);
            }
            else if (P >= 25 && P < 30)
            {
                FAN_PWM_SetCCR(40);
            }
            else if (P >= 30 && P < 35)
            {
                FAN_PWM_SetCCR(50);
            }
            else if (P >= 35 && P < 40)
            {
                FAN_PWM_SetCCR(60);
            }
            else if (P >= 40 && P < 45)
            {
                FAN_PWM_SetCCR(70);
            }
            else if (P >= 45 && P < 50)
            {
                FAN_PWM_SetCCR(80);
            }
            else if (P >= 50 && P < 60)
            {
                FAN_PWM_SetCCR(90);
            }
            else if (P >= 60)
            {
                FAN_PWM_SetCCR(100);
            }
        }
        else if (P <= 8)
        {
            FAN_PWM_SetCCR(0); // ファンのPWM出力をOFF、ローレベル出力
            FAN_PWM_OFF();
        }
        rt_thread_mdelay(200);
    }
}

/* スレッド9のエントリ関数、定電力・定抵抗モード制御 */
static void thread9_CWCR_entry(void *parameter)
{
    while (1)
    {
        if (mode == CW)
        {
            CW_mode();
        }
        if (mode == CR)
        {
            CR_mode();
        }
        rt_thread_mdelay(50);
    }
}/* スレッド10のエントリ関数、Bluetoothからのデータを処理 */
static void thread10_BlueTooth_entry(void *parameter)
{
    HMILCD_Send("CC.x0.val=0"); // 画面に表示される電流設定値をクリア
    HMILCD_Send("CV.x0.val=0"); // 画面に表示される電圧設定値をクリア
    HMILCD_Send("CR.x0.val=0"); // 画面に表示される抵抗設定値をクリア
    HMILCD_Send("CW.x0.val=0"); // 画面に表示される電力設定値をクリア
    while (1)
    {
        if (Serial5_RxFlag == 1)
        {
            if (Serial5_RxPacket[0] == 0x01) // 現在はメインメニューページ
            {
                if (Serial5_RxPacket[1] == 0x10) // 定電流ボタンが押された
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    Eload_Out = 0;
                    HMILCD_Send("page CC"); // 定電流モードページに切り替え
                    mode = CC;                    // 現在のモードを定電流モードに設定
                }
                else if (Serial5_RxPacket[1] == 0x11) // 定電圧ボタンが押された
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    Eload_Out = 0;
                    HMILCD_Send("page CV"); // 定電圧ページに切り替え
                    mode = CV;                    // 現在のモードを定電圧モードに設定
                }
                else if (Serial5_RxPacket[1] == 0x12) // 定抵抗ボタンが押された
                {
                    if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    else if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CW)
                    {
                        HMILCD_Send("CW.t1.txt=\"OFF\"");
                        HMILCD_Send("CW.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    Eload_Out = 0;
                    HMILCD_Send("page CR"); // 定抵抗ページに切り替え
                    mode = CR;                    // 現在のモードを定抵抗モードに設定
                }
                else if (Serial5_RxPacket[1] == 0x13) // 定電力ボタンが押された
                {
                    if (mode == CV)
                    {
                        HMILCD_Send("CV.t1.txt=\"OFF\"");
                        HMILCD_Send("CV.b1.txt=\"开启\"");

                    }
                    else if (mode == CR)
                    {
                        HMILCD_Send("CR.t1.txt=\"OFF\"");
                        HMILCD_Send("CR.b1.txt=\"开启\"");

                    }
                    else if (mode == CC)
                    {
                        HMILCD_Send("CC.t1.txt=\"OFF\"");
                        HMILCD_Send("CC.b1.txt=\"开启\"");

                    }
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    Eload_Out = 0;
                    HMILCD_Send("page CW"); // 定電力ページに切り替え
                    mode = CW;                    // 現在のモードを定電力モードに設定
                }
            }
            else if (Serial5_RxPacket[0] == 0x08)    // メニューに戻る
            {
                if (mode == CC)
                {
                    HMILCD_Send("CC.t1.txt=\"OFF\"");  // 画面右上のタイトルボックスにOFFを表示
                    HMILCD_Send("CC.b1.txt=\"开启\""); // 画面右下のボタンに开启を表示
                    Eload_Out = 0;                           // 負荷出力状態をオフに設定
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");          // メニューページに切り替え
                    mode = menu;
                }
                else if (mode == CV)
                {
                    HMILCD_Send("CV.t1.txt=\"OFF\"");
                    HMILCD_Send("CV.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CR)
                {
                    HMILCD_Send("CR.t1.txt=\"OFF\"");
                    HMILCD_Send("CR.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");
                    mode = menu;
                }
                else if (mode == CW)
                {
                    HMILCD_Send("CW.t1.txt=\"OFF\"");
                    HMILCD_Send("CW.b1.txt=\"开启\"");
                    Eload_Out = 0;
                    DAC_SetChannel1Data(DAC_Align_12b_R, 4095); // DAC1はハイレベル出力、定電圧をオフ
                    DAC_SetChannel2Data(DAC_Align_12b_R, 0);    // DAC2はローレベル出力、定電流をオフ
                    PWM_SetCCR4(0);  // IREF2を設定
                    HMILCD_Send("page menu");
                    mode = menu;
                }
            }
            else if (Serial5_RxPacket[0] == 0x09)    // オンボタン
            {
                Key_ONOFF = 1;
            }

            else if (Serial5_RxPacket[0] == 0xAA) // 現在は数字キーボードページ
            {
                char *temp = Serial5_RxPacket;
                temp++;                      // アドレスを1増加
                uint16_t temp2 = atoi(temp); // 文字列を整数に変換
                if (mode == CC)
                {
                    if (temp2 > 1000)
                        temp2 = 1000;
                    ISET = temp2 / 100.0;
                    HMILCD_Send("CC.x0.val=%d", temp2);if (Eload_Out == 1)
                    {
                        DAC_SetChannel1Data(DAC_Align_12b_R, 0);
                        if (ISET <= 2.5)
                        {
                            // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入用
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4(0);  // IREF2を設定
                        }
                        else
                        {
                            // DAC2出力値を設定し、定電流を制御、+0.5は四捨五入用
                            DAC_SetChannel2Data(DAC_Align_12b_R,
                                    (uint16_t)(ISET / 2.0 * 0.01 * 50 * 4096 / VREF * DAC2_COMP + 0.5));
                            PWM_SetCCR4((uint16_t)(ISET / 2.0 * 0.01 * 50 / VCC * 50000 * IREF2_COMP + 0.5)); // IREF2を設定
                        }

                    }

                }
                else if (mode == CV)
                {
                    VSET = temp2 / 100.0;
                    HMILCD_Send("CV.x0.val=%d", temp2);
                    if (Eload_Out == 1)
                    {
                        DAC_SetChannel2Data(DAC_Align_12b_R, 4095); // IREF1を設定
                        PWM_SetCCR4(50000);  // IREF2を設定
                        if (voltage_dw == 0)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0325 * 4096 / VREF * DAC1_COMP + 0.5)); // DAC1出力値を設定し、定電圧を制御
                        }
                        else if (voltage_dw == 1)
                        {
                            DAC_SetChannel1Data(DAC_Align_12b_R,
                                    (uint16_t)(VSET * 0.0947 * 4096 / VREF * DAC1_COMP + 0.5)); // DAC1出力値を設定し、定電圧を制御
                        }
                        else if (voltage_dw == 2)
                        {
                            uint16_t vset_pwm = (uint16_t)(VSET * 0.6175 * 4096 / VREF * DAC1_COMP + 0.5);
                            if (vset_pwm > 4065)
                                vset_pwm = 4095;
                            DAC_SetChannel1Data(DAC_Align_12b_R, vset_pwm); // DAC1出力値を設定し、定電圧を制御
                        }
                    }
                }
                else if (mode == CR)
                {
                    RSET = temp2 / 100.0;
                    HMILCD_Send("CR.x0.val=%d", temp2);
                    CR_mode();
                }
                else if (mode == CW)
                {
                    PSET = temp2 / 100.0;
                    HMILCD_Send("CW.x0.val=%d", temp2);
                    CW_mode();
                }
            }
            Serial5_RxFlag = 0;
        }
        rt_thread_mdelay(40);
    }

}

おすすめ記事