顯示具有 程式語言開發 標籤的文章。 顯示所有文章
顯示具有 程式語言開發 標籤的文章。 顯示所有文章

2026年9月1日 星期二

Agilent 34401A 萬用電表 控制介面設計

📺 影片版(約 7 分鐘):想先看實際操作的話可以直接看這支影片,完整步驟、設定與程式碼在下方文字版。
YouTube:https://youtu.be/mpRLVVXrFiQ(winson-DIY 頻道)
最近把閒置的儀器, 全部拿出來檢查是否還可用並寫GUI來控制充分利用一下!
34401A 萬用電表

設計概念:
1. 連接的GPIB介面輸入
2. 電表功能選擇
3. Chart圖形顯示語文字顯示
4. 輸出log顯示與儲存

實際執行驗證結果:
由power supply輸出DC12V由電表量測!



補充一、34401A 規格重點
34401A 是 HP(後來的 Agilent、現在的 Keysight)出的 6½ 位數桌上型萬用電表,雖然是老機種,到現在還是很多實驗室的標準配備:
項目規格
解析度6½ 位數(可切 4½ / 5½ / 6½)
DC 電壓100 mV ~ 1000 V,5 個檔位;10 V 檔 1 年準確度 0.0035% 讀值 + 0.0005% 檔位
AC 電壓100 mV ~ 750 V(True RMS)
DC / AC 電流DC 10 mA ~ 3 A;AC 1 A、3 A
電阻100 Ω ~ 100 MΩ,支援 2 線 / 4 線
其他頻率、週期、導通、二極體
介面GPIB(IEEE-488)、RS-232
讀值速度最快約 1000 筆/秒(4½ 位、NPLC 0.02)

小提醒:除了 1000 V 檔,各檔位都有 20% 超量程,所以 12 V 左右的訊號在自動檔位下仍會落在 10 V 檔,可以解析到 1 µV。

補充二、連線架構與 GPIB 位址
PC(C# WinForms)
   │  VISA(Keysight IO Libraries 或 NI-VISA)
   ▼
GPIB 介面卡 / USB-GPIB 轉接器(板號 GPIB0、GPIB1…)
   │  GPIB 線
   ▼
34401A(GPIB 位址,出廠預設 22,這台設成 24)
VISA 資源字串格式是 GPIB板號::位址::INSTR,這次實際用的是 GPIB1::24::INSTR。
從驗證截圖下方的 log 可以看到,前 4 次都是 Open Instrument Fail!!,把 GPIB 下拉選單從 0 改成 1 之後才連上,儀器回傳的 *IDN? 是:
HEWLETT-PACKARD,34401A,0,11-5-2
重點:電腦上如果裝過不只一張 GPIB 卡或轉接器,板號不一定是 0。開不起來時先用 Keysight Connection Expert 或 NI MAX 掃一次,確認板號和位址,比在程式裡亂試快很多。儀器的位址可以在前面板 Shift → Menu 的 I/O 選單裡查。

補充三、常用 SCPI 指令
GUI 上的「量測功能選擇」,背後就是對應到不同的 SCPI 指令:
功能一次量測(MEASure)先設定(CONFigure)
DC 電壓MEAS:VOLT:DC?CONF:VOLT:DC
AC 電壓MEAS:VOLT:AC?CONF:VOLT:AC
DC 電流MEAS:CURR:DC?CONF:CURR:DC
AC 電流MEAS:CURR:AC?CONF:CURR:AC
2 線電阻MEAS:RES?CONF:RES
4 線電阻MEAS:FRES?CONF:FRES
頻率 / 週期MEAS:FREQ? / MEAS:PER?CONF:FREQ / CONF:PER
導通 / 二極體MEAS:CONT? / MEAS:DIOD?CONF:CONT / CONF:DIOD

其他常用的:
  • *IDN?:讀儀器型號,拿來確認連線
  • *RST;*CLS:重置並清除錯誤狀態
  • READ?:依目前設定觸發一次並讀回
  • VOLT:DC:NPLC 10:積分時間(0.02 / 0.2 / 1 / 10 / 100),越大越準越慢
  • SYST:ERR?:讀錯誤佇列,指令沒反應時先查這個
連續量測的小技巧:MEAS:...? 每次都會重新設定儀器,連續量測會比較慢。比較好的做法是開始時 CONF 一次,之後在迴圈裡只送 READ?。

補充四、C# 程式骨架(VISA.NET)
以下是「連線 → 設定 → 連續讀值」的最小範例,用的是 Keysight IO Libraries 和 NI-VISA 都有提供的 VISA.NET(Ivi.Visa):
using System.Globalization;
using Ivi.Visa;

// 連線
var dmm = (IMessageBasedSession)GlobalResourceManager.Open("GPIB1::24::INSTR");
dmm.TimeoutMilliseconds = 5000;
dmm.Clear();
var io = dmm.FormattedIO;

io.WriteLine("*RST;*CLS");
io.WriteLine("*IDN?");
string idn = io.ReadLine();            // HEWLETT-PACKARD,34401A,0,11-5-2

// 設定一次:DC 電壓、自動檔位
io.WriteLine("CONF:VOLT:DC DEF,DEF");
io.WriteLine("VOLT:DC:NPLC 10");

// 連續讀值(放在背景 Task,按 Stop 時取消)
var sw = System.Diagnostics.Stopwatch.StartNew();
while (!token.IsCancellationRequested)
{
    io.WriteLine("READ?");
    double v = double.Parse(io.ReadLine(), CultureInfo.InvariantCulture);  // "+1.19872270E+01"
    double t = sw.Elapsed.TotalSeconds;
    // UI 執行緒更新:Chart 加點 (t, v)、Logs 加一行
    // $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] | {v:F6} -- V"
}

// 結束
dmm.Dispose();
幾個實作重點:
  • 量測迴圈不要放在 UI 執行緒:用 Task + CancellationToken,Stop 按鈕只負責取消,畫面才不會卡住
  • 解析讀值要用 InvariantCulture:儀器回的是科學記號字串,系統地區設定不同時可能解析錯誤
  • Chart 用時間當 X 軸:驗證截圖的 X 軸是「時間(秒)」,只保留最近一段(例如 200 點)視窗才不會越畫越慢
  • Timeout 一定要設:儀器關機或線鬆了,ReadLine 才不會一直卡住
  • 斷線後回本地操作:程式結束後前面板若顯示 Rmt 鎖住,按 Shift(Local)就能回到手動操作

補充五、實測結果解讀
驗證時由 power supply 輸出 DC 12 V,log 裡連續 9 筆讀值:
  • 讀值介於 11.987227 ~ 11.987254 V,跳動只有約 27 µV(約 2 ppm),讀值相當穩定
  • 跟 12 V 設定值差約 13 mV(−0.1%),這是 power supply 本身的輸出誤差,不是電表的問題,剛好可以拿 34401A 來校正電源的實際輸出
  • 相鄰兩筆的時間差約 0.43 秒,也就是大約每秒 2.3 筆,以 NPLC 10 的設定來說是合理的;需要更快可以把 NPLC 調小,但雜訊會變大

補充六、延伸應用
  • Logs 存成 CSV(時間, 讀值, 單位),之後可以用 Excel 或 Python 做長時間的穩定度分析
  • 搭配 power supply 一起程式控制,就能做自動化的電壓掃描或負載測試
  • 如果要一次量很多點,可以搭配 34970A 切換開關,做法可以參考之前的文章:PCBA電壓檢測項目-Aglient 34970A


延伸閱讀

2026年2月22日 星期日

Arduino Mega2560 EEPROM 的應用

問題: arduino mega2560 接電源接頭 跟 UART後設定完後斷開UART後設定值不見的問題 
在 Arduino 設計中,當序列埠 (Serial Port) 被開啟或關閉(例如拔掉 USB 或關閉序列監控視窗)時,DTR 訊號會觸發晶片重設。
這通常是因為 Arduino Mega 2560 的預設自動重設 (Auto-Reset) 機制,以及變數儲存在 RAM (隨機存取記憶體) 中導致的。
可以使用內建的 EEPROM 來保存設定!!
引入函式庫:#include <EEPROM.h>。
儲存設定:使用 EEPROM.put(address, data)。
讀取設定:在 setup() 中使用 EEPROM.get(address, data) 載入舊值。

#include <EEPROM.h>
// ==========================================================
// EEPROM 記憶體位址定義
// ==========================================================
const int MAGIC_NUMBER_ADDR = 0;
const int SETTINGS_ADDR = sizeof(byte); // 設定結構體的起始位址

const byte MAGIC_VALUE = 0xAC; // 自訂一個魔術數字

const int NUM_RELAYS = 16;
// ==========================================================
// 1. 定義設定的結構體
// ==========================================================
struct RelaySettings {
  byte orgConfigNumber;
  byte AllRelayFlag;
  byte RunConfigFlag;
  byte ConfigNumber;
  uint8_t ConfigFlag[NUM_RELAYS];
  unsigned long DelayTime ;  //--設定 relay1~16延遲打開時間
  unsigned long OnTime_Arr[NUM_RELAYS];
  unsigned long OffTime_Arr[NUM_RELAYS];
};

// ==========================================================
// 宣告一個全域變數來存放我們的設定
// ==========================================================
RelaySettings relayConfig;

// ==========================================================
// 輔助函數:載入您指定的預設值,並將其寫入 EEPROM
// ==========================================================
void SaverelayConfig() {
// 使用 EEPROM.put() 將整個結構體一次性寫入 EEPROM
  EEPROM.put(SETTINGS_ADDR, relayConfig);
 
  // 寫入魔術數字,標記 EEPROM 已被成功初始化
  EEPROM.put(MAGIC_NUMBER_ADDR, MAGIC_VALUE);

  Serial.println("已成功載入並儲存到 EEPROM。");

  }

void setup() {
Serial.println("\n--- Arduino EEPROM 陣列範例啟動 ---");
// ========================================================== // 檢查 EEPROM 中的魔術數字,判斷陣列資料是否有效 // ========================================================== byte storedMagicValue; EEPROM.get(MAGIC_NUMBER_ADDR, storedMagicValue); if (storedMagicValue == MAGIC_VALUE) { // 如果魔術數字匹配,表示 EEPROM 已經被初始化過,可以安全讀取陣列 Serial.println("偵測到有效設定,從 EEPROM 載入..."); EEPROM.get(SETTINGS_ADDR, relayConfig); } else { // 如果不匹配,表示首次運行或資料損毀,則載入預設值並存入 EEPROM Serial.println("EEPROM 無有效設定,載入預設值並儲存..."); loadDefaultsAndSave(); } printCurrentSettings(); // 顯示目前載入的設定 Serial.println("\n輸入 'reset' 將所有設定恢復為預設值。");

}













延伸閱讀

2025年2月4日 星期二

坦聯 旋轉轉台控制小工具

執行用C#寫的MDI_ATS程式, 選擇ATS中的Console, 再依據轉台所接的轉接板com port 開啟所用的port number

硬體包括坦聯控制盒, 單軸轉台
依據Unit 的轉動角度有1, 10 , 20 , 100度再按button +(正轉動)或是 button –(負轉動), 單次轉動轉台




延伸閱讀

2024年9月1日 星期日

Tektronix MSO64B C# GUI Application

Purpose:
為了長時間去偵測TekTronix的MSO64B量到的數據並記錄下來, 因此才有這個專案產生. 利用C#寫的GUI來做這個動作!
Fundamental:
TEKTRONIX太克
6B 系列MSO 混合訊號示波器
GUI:
包含連接儀器控制, 儀器channel顯示控制, 個別channel 顯示 和即時儲存量測資料

Demo:





延伸閱讀

2024年8月13日 星期二

C# 利用ffmpeg將WebCAM影像Streaming to RTSP server

Purpose:
利用C#架構一個RTSP 影像streaming的環境.包含WebCAM控制, FFMpeg使用 and Emgu.CV的應用 
Fundamental:
RTSP
即時串流協定(Real Time Streaming Protocol,RTSP)是一種網路應用協定,專為娛樂和通訊系統的使用,以控制串流媒體伺服器。該協定用於建立和控制終端之間的媒體對談。媒體伺服器的客戶端發布VCR命令,例如播放,錄製和暫停,以便於即時控制從伺服器到客戶端(影片點播)或從客戶端到伺服器(語音錄音)的媒體流。
FFmpeg
FFmpeg 是一個開放原始碼的自由軟體,它包含了音訊和視訊多種格式的錄影、轉檔、串流功能,同時也是一個音訊與視訊格式轉換函式庫(Library),許多開源的工具都是基於 FFmpeg 打造的。

Reference:
感謝分享:包含 RTSP windows server 和一些相關軟體介紹與下載!!
public void StartRTSP(string path)
    {
       
        if (_RTSPPara._process != null && !_RTSPPara._process.HasExited)
            return;

        _RTSPPara._process = new Process();
        _RTSPPara._process.StartInfo.UseShellExecute = false;
        _RTSPPara._process.StartInfo.FileName = @path;
        _RTSPPara._process.StartInfo.Arguments = "";
        _RTSPPara._process.StartInfo.RedirectStandardInput = true;
        _RTSPPara._process.StartInfo.RedirectStandardOutput = true;
        _RTSPPara._process.StartInfo.RedirectStandardError = true;
        _RTSPPara._process.EnableRaisingEvents = true;
        _RTSPPara._process.StartInfo.CreateNoWindow = true;
        try
        {
            var started = _RTSPPara._process.Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
        }

        _RTSPPara._process.BeginErrorReadLine();
        _RTSPPara._process.BeginOutputReadLine();
    }


public void StartFFmpeg(string path, string webname)
    {
        string rtspServer = "rtsp://localhost:8554/test";
        string ffmpegCommand =
                $"-f dshow -i video=\"{webname}\" -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -rtsp_transport tcp -f rtsp {rtspServer}";
        if (_FFmpegPara._process != null && !_FFmpegPara._process.HasExited)
            return;

        _FFmpegPara._process = new Process();
        _FFmpegPara._process.StartInfo.UseShellExecute = false;
        _FFmpegPara._process.StartInfo.FileName = @path;
        _FFmpegPara._process.StartInfo.Arguments = ffmpegCommand;
        _FFmpegPara._process.StartInfo.RedirectStandardInput = true;
        _FFmpegPara._process.StartInfo.RedirectStandardOutput = true;
        _FFmpegPara._process.StartInfo.RedirectStandardError = true;
        _FFmpegPara._process.EnableRaisingEvents = true;
        _FFmpegPara._process.StartInfo.CreateNoWindow = true;
        _FFmpegPara._process.StartInfo.Verb = "RunAs";
        try
        {
            var started = _FFmpegPara._process.Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
        }

        _FFmpegPara._process.BeginErrorReadLine();
        _FFmpegPara._process.BeginOutputReadLine();
    }

        private void button7_Click(object sender, EventArgs e)
        {

            if (FFmpeg._FFmpegPara.RunisFinish == true)
            {
                //button5.BackColor = Color.AliceBlue;
                button7.BackColor = Color.Green;
                WebCAM[0].show_screen = true;
                myVideoCapture = new VideoCapture("rtsp://127.0.0.1:8554/test");
                myVideoCapture.ImageGrabbed += imageGrabbedEvent;
                myVideoCapture.Start();
            }
            else if (WebCAM[0].run == true)
            {
                //button5.BackColor = Color.Green;
                button7.BackColor = Color.AliceBlue;
                if (myVideoCapture != null)
                {
                    myVideoCapture.ImageGrabbed -= imageGrabbedEvent;
                    Thread.Sleep(500);
                    myVideoCapture.Dispose();
                }

            }
        }

        public void imageGrabbedEvent(object sender, EventArgs arg)
        {
            try
            {
                TEmgu._EmguPara.ImageSource = myVideoCapture.QueryFrame().ToImage<Bgr, byte>();
            }
            catch (Exception Ex)
            {

            }
            if (TEmgu._EmguPara.ImageSource != null)
            {

                imageBox1.Image = TEmgu._EmguPara.ImageSource.Resize(imageBox1.Width, imageBox1.Height, Emgu.CV.CvEnum.Inter.Linear);

            } //----if (TEmgu._EmguPara.ImageSource != null)
           

        }


延伸閱讀

2024年7月9日 星期二

C# 讀取超大檔案方式

Purpose:
在專案中, 有一需求是去搜尋一些500MB個別檔案重複資料, 嘗試了很多讀取檔案的方式. 最後試出來最快的方法.
Method:
using System.IO;

string strLine;
const int MAX_BUFFER = 33554432; //32MB 
using (FileStream fs = File.Open(csvFile, FileMode.Open, FileAccess.Read))
using (BufferedStream bs = new BufferedStream(fs, MAX_BUFFER)) 
using (StreamReader sr = new StreamReader(bs))
{  
    while (!sr.EndOfStream)
   {
        while ((strLine = sr.ReadLine()) != null)
       {
               //---處理資料
        }

    }
    sr.Close();
    sr.Dispose();
}

希望對你們有幫助!!


延伸閱讀

2024年4月4日 星期四

C# 讀取Bin檔案並修改其內容


FileStream file_path = new FileStream(@BinFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
BinaryReader bin_read = new BinaryReader(file_path);

int dl = System.Convert.ToInt32(file_path.Length);
//讀取位元陣列
byte[] InData;
//讀取資料
InData = bin_read.ReadBytes(dl);
//釋放資源
bin_read.Close();
file_path.Close();

                 
FileStream temp_file_path = new FileStream(@TempBinFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
BinaryWriter bin_write = new BinaryWriter(temp_file_path);//創建BIN文件流
bin_write.Write(InData);
bin_write.Seek(8, SeekOrigin.Begin);//修改BIN文件位置從第8字節

bin_write.Write((byte)0x01);//第8字節改為01
bin_write.Write((byte)0x02);//第9字節改為02
bin_write.Write((byte)(0x90));//第10字節改為90

bin_write.Close();
temp_file_path.Close();




延伸閱讀

2023年8月6日 星期日

Control WS2812B Addressable LEDs with ESP32

Purpose:

WS2812 RGB LED, it is an LED with a built-in driver chip function, and uses a 5050 LED package to add a driver chip, and the driving method adopts serial input and output.
We will learn how to install WS2812 on an ESP32 board and use it to control a string of addressable LEDs.

Circuit:

You can use Wokwi simulator to test the program

YouTubeDemo:


CodeIntroduce:

#include <Adafruit_NeoPixel.h>
//--------- Flag structure --------------------------------------
typedef struct _vFlag
{
  uint8_t LEDFlag=1;
  uint8_t BTFlag = 0;
  uint8_t ServoFlag = 0;
  uint8_t WS2812BMode = 0;
} vFlag;
vFlag *flag_Ptr;
vFlag flag;
//----------uart--------------
#define LINE_BUFFER_LENGTH 64
//--------- uart structure --------------------------------------
typedef struct _vUart
{
  char c;
  int lineIndex = 0;
  int line1Index = 0;
  int BTlineIndex = 0;
  bool lineIsComment;
  bool lineSemiColon;
  char line[128];
  char BTline[20];
  String inputString;
  String BTinputString;
  String S1inputString;
  int V[16];
  char ctemp[30];
  char I2C_Data[80];
  int DC_Spped = 50;
  float Voltage[16];
  int Buffer[128];
  int StartCnt = 0;
  int ReadCnt = 0;
  int sensorValue = 0;
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//------------------------------------------------
#define LED_BUILTIN 2
//------------------------------------------------
// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN     15
// How many NeoPixels are attached to the Arduino?
#define LED_COUNT  16
// NeoPixel brightness, 0 (min) to 255 (max)
#define BRIGHTNESS 220 // Set BRIGHTNESS to about 1/5 (max = 255)
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)

void setup()
{
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(BRIGHTNESS);
  Serial.println(F("System On!"));
}

void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while(1)
  {
    if(flag.LEDFlag == 1)
    {
      digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
      vTaskDelay(300);
      digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
      vTaskDelay(300);
    }
    while (Serial.available() > 0)
    {
      Uart.c = Serial.read();

      if ((Uart.c == '\n') || (Uart.c == '\r'))
      { // End of line reached
        if (Uart.lineIndex > 0)
        { // Line is complete. Then execute!
          Uart.line[Uart.lineIndex] = '\0'; // Terminate string
          processCommand(Uart.line); // do something with the command
         
          Uart.lineIndex = 0;
          Uart.inputString = "";
        }
        else
        {
          // Empty or comment line. Skip block.
        }
        Uart.lineIsComment = false;
        Uart.lineSemiColon = false;
        Serial.println(F("ok>"));
      }
      else
      {
        //Serial.println( c );
        if ((Uart.lineIsComment) || (Uart.lineSemiColon))
        {
          if (Uart.c == ')')
            Uart.lineIsComment = false; // End of comment. Resume line.
        }
        else
        {
          if (Uart.c == '/')
          { // Block delete not supported. Ignore character.
          }
          else if (Uart.c == '~')
          { // Enable comments flag and ignore all characters until ')' or EOL.
            Uart.lineIsComment = true;
          }
          else if (Uart.c == ';')
          {
            Uart.lineSemiColon = true;
          }
          else if (Uart.lineIndex >= LINE_BUFFER_LENGTH - 1)
          {
            Serial.println("ERROR - lineBuffer overflow");
            Uart.lineIsComment = false;
            Uart.lineSemiColon = false;
          }
          else if (Uart.c >= 'a' && Uart.c <= 'z')
          { // Upcase lowercase
            Uart.line[Uart.lineIndex] = Uart.c - 'a' + 'A';
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += (char)(Uart.c - 'a' + 'A');
          }
          else
          {
            Uart.line[Uart.lineIndex] = Uart.c;
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += Uart.c;
          }
        }
      }
    } //while (Serial.available() > 0)

  }
}//----loop
//-----------------------------------------------
void processCommand(char *data)
{
  int len, xlen, ylen, zlen, alen;
  int tempDIO;
  String stemp;

  len = Uart.inputString.length();
  //---------------------------------------
  if (strstr(data, "VER") != NULL)
  {
    Serial.println(F("ESP32_20230806"));
  }
  if (strstr(data, "MODE1") != NULL)
  {
    flag.WS2812BMode=1;
    rainbowCycle(5);
    //whiteOverRainbow(75, 5);
  }
  if (strstr(data, "MODE2") != NULL)
  {
    for(int i=0;i<LED_COUNT;i++)
    {
      strip.setPixelColor(i, strip.Color(255, 255, 255, BRIGHTNESS)); // Set white
    }
    strip.show();
    flag.WS2812BMode=2;
    //rainbowCycle(5);
    //whiteOverRainbow(75, 5);
  }
}
//------------------------------------------------
void whiteOverRainbow(int whiteSpeed, int whiteLength) {

  if(whiteLength >= strip.numPixels()) whiteLength = strip.numPixels() - 1;

  int      head          = whiteLength - 1;
  int      tail          = 0;
  int      loops         = 3;
  int      loopNum       = 0;
  uint32_t lastTime      = millis();
  uint32_t firstPixelHue = 0;

  for(;;) { // Repeat forever (or until a 'break' or 'return')
    for(int i=0; i<strip.numPixels(); i++) {  // For each pixel in strip...
      if(((i >= tail) && (i <= head)) ||      //  If between head & tail...
         ((tail > head) && ((i >= tail) || (i <= head)))) {
        strip.setPixelColor(i, strip.Color(0, 0, 0, 255)); // Set white
      } else {                                             // else set rainbow
        int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
        strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
      }
    }

    strip.show(); // Update strip with new contents
    // There's no delay here, it just runs full-tilt until the timer and
    // counter combination below runs out.

    firstPixelHue += 40; // Advance just a little along the color wheel

    if((millis() - lastTime) > whiteSpeed) { // Time to update head/tail?
      if(++head >= strip.numPixels()) {      // Advance head, wrap around
        head = 0;
        if(++loopNum >= loops) return;
      }
      if(++tail >= strip.numPixels()) {      // Advance tail, wrap around
        tail = 0;
      }
      lastTime = millis();                   // Save time of last movement
    }
  }
}

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
//------------------------------------------------





延伸閱讀

2023年7月16日 星期日

ESP32 Bluetooth SPP control -- DC motors with L298N module

Purpose:

To control one or two DC motors is quite easy with the L298N H-bridge module. First connect each motor to the A and B connections on the L298N module.

If you’re using two motors for a robot (etc) ensure that the polarity of the motors is the same on both inputs. Otherwise you may need to swap them over when you set both motors to forward and one goes backwards!


L298N introduce:
L298N Dual H-Bridge Motor Driver
This dual bidirectional motor driver, is based on the very popular L298 Dual H-Bridge Motor DriverIntegrated Circuit. 
The circuit will allow you to easily and independently control two motors of up to 2A each in both directions.It is ideal for robotic applications and well suited for connection to a microcontroller requiring just a couple of control lines per motor. It can also be interfaced with simple manual switches, TTL logic gates, relays, etc. This board equipped with power LED indicators, on-board +5V regulator and protection diodes.

Circuit:


ESP32 Code Introduce:
#include <Robojax_L298N_DC_motor.h>
#include <BluetoothSerial.h>
//--------- Flag structure --------------------------------------
typedef struct _vFlag
{
  uint8_t BTFlag = 0;
  uint8_t DC_Flag = 0;
  uint8_t CANFlag = 0;
  uint8_t I2C_Flag = 0;
  uint8_t BMP180Flag = 0;
  uint8_t DS18B20Flag = 0;
  uint8_t JSONFlag = 0;
  uint8_t Radar_L_Flag = 0;
  uint8_t Radar_R_Flag = 0;
  uint8_t sensor_Flag = 0;
  uint8_t sensor1_Flag = 0;
  uint8_t initial_Flag = 0;
  uint8_t Tone_Flag = -1;
  uint8_t IR_RECV_Flag=0;
  uint8_t IR_SEND_Flag=0;
  uint8_t FunctionFlag = 3;
  uint8_t SendFlag = 0;
  uint8_t BMPCnt = 0;
} vFlag;
vFlag *flag_Ptr;
vFlag flag;

//----------uart--------------
#define LINE_BUFFER_LENGTH 64
//--------- uart structure --------------------------------------
typedef struct _vUart
{
  char c;
  int lineIndex = 0;
  int line1Index = 0;
  int BTlineIndex = 0;
  bool lineIsComment;
  bool lineSemiColon;
  char line[128];
  char BTline[20];
  String inputString;
  String BTinputString;
  String S1inputString;
  int V[16];
  char ctemp[30];
  char I2C_Data[80];
  int DC_Spped = 50;
  float Voltage[16];
  int Buffer[128];
  int StartCnt = 0;
  int ReadCnt = 0;
  int sensorValue = 0;
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//---------BT--------------------
BluetoothSerial SerialBT;
//-------------L298---------------------------------------------------
// motor 1 settings
#define CHA 0
#define ENA 4 // this pin must be PWM enabled pin if Arduino board is used
#define IN1 16
#define IN2 17
// motor 2 settings
#define IN3 18
#define IN4 19
#define ENB 5// this pin must be PWM enabled pin if Arduino board is used
#define CHB 1

const int CCW = 2; // do not change
const int CW  = 1; // do not change

#define motor1 1 // do not change
#define motor2 2 // do not change

// for two motors without debug information // Watch video instruciton for this line: https://youtu.be/2JTMqURJTwg
Robojax_L298N_DC_motor motors(IN1, IN2, ENA, CHA,  IN3, IN4, ENB, CHB);

void Forward() //電機前進
{
  motors.brake(1);  
  motors.brake(2);  

  motors.rotate(motor1, 70, CCW);
  motors.rotate(motor2, 70, CCW); 
}

void Reverse(){
  motors.brake(1);  
  motors.brake(2);  

  motors.rotate(motor1, 70, CW);
  motors.rotate(motor2, 70, CW);  
}
void Left()
{
  motors.brake(1);  
  motors.brake(2);  
  motors.rotate(motor1, 70, CW);//run motor2 at 60% speed in CCW direction  
}
void Right()
{
  motors.brake(1);  
  motors.brake(2);  
  motors.rotate(motor2, 70, CW);
}

void Stop() //電機停止
{
  motors.brake(1);
  motors.brake(2);  
}
//-------------------------------------------------
void setup()
{
  Serial.begin(9600);
  Serial.println(F("init"));
  SerialBT.begin("BT_Node12");// BTName為藍芽廣播名稱
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  pinMode(ENA, OUTPUT);
  pinMode(ENB, OUTPUT);

}
//-----------------------------------------
void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while(1)
  {
    while (Serial.available() > 0)
    {
      Uart.c = Serial.read();

      if ((Uart.c == '\n') || (Uart.c == '\r'))
      { // End of line reached
        if (Uart.lineIndex > 0)
        { // Line is complete. Then execute!
          Uart.line[Uart.lineIndex] = '\0'; // Terminate string
          processCommand(Uart.line); // do something with the command
          Uart.lineIndex = 0;
          Uart.inputString = "";
        }
        else
        {
          // Empty or comment line. Skip block.
        }
        Uart.lineIsComment = false;
        Uart.lineSemiColon = false;
        Serial.println(F("ok>"));
      }
      else
      {
        if ((Uart.lineIsComment) || (Uart.lineSemiColon))
        {
          if (Uart.c == ')')
            Uart.lineIsComment = false; // End of comment. Resume line.
        }
        else
        {
          if (Uart.c == '/')
          { // Block delete not supported. Ignore character.
          }
          else if (Uart.c == '~')
          { // Enable comments flag and ignore all characters until ')' or EOL.
            Uart.lineIsComment = true;
          }
          else if (Uart.c == ';')
          {
            Uart.lineSemiColon = true;
          }
          else if (Uart.lineIndex >= LINE_BUFFER_LENGTH - 1)
          {
            Serial.println("ERROR - lineBuffer overflow");
            Uart.lineIsComment = false;
            Uart.lineSemiColon = false;
          }
          else if (Uart.c >= 'a' && Uart.c <= 'z')
          { // Upcase lowercase
            Uart.line[Uart.lineIndex] = Uart.c - 'a' + 'A';
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += (char)(Uart.c - 'a' + 'A');
          }
          else
          {
            Uart.line[Uart.lineIndex] = Uart.c;
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += Uart.c;
          }
        }
      }
    } //while (Serial.available() > 0)
    //檢查藍芽內是否有資料
    while (SerialBT.available())
    {
      //讀取藍芽資料
      String BTdata = SerialBT.readString();
      //顯示在序列視窗
      Serial.println(BTdata);
      BTprocessCommand(BTdata); // do something with the command

    }//while (BT.available())
 

  }
}
//-------------------BT-----------------
void BTprocessCommand(String data)
{
  if (data =="F")
  {
    Serial.println(F("Forward"));
    Forward();
  }
  if (data == "L")
  {
    Serial.println(F("Left"));
    Left();
  }
  if (data == "R")
  {
    Serial.println(F("Right"));
    Right();
  }
  if (data == "B")
  {
    Serial.println(F("Reverse"));
    Reverse();
  }
  if (data == "S")
  {
    Serial.println(F("Stop"));
    Stop();
  }
}
//----------------------------------------
void processCommand(char *data)
{
  int len, xlen, ylen, zlen, alen;
  int tempDIO;
  String stemp;

  len = Uart.inputString.length();
  //---------------------------------------
  if (strstr(data, "VER") != NULL)
  {
    Serial.println(F("ESP32_20230710"));
  }
  if (strstr(data, "F") != NULL)
  {
    Serial.println(F("Forward"));
    Forward();
  }
  if (strstr(data, "L") != NULL)
  {
    Serial.println(F("Left"));
    Left();
  }
  if (strstr(data, "R") != NULL)
  {
    Serial.println(F("Right"));
    Right();
  }
  if (strstr(data, "B") != NULL)
  {
    Serial.println(F("Reverse"));
    Reverse();
  }
  if (strstr(data, "S") != NULL)
  {
    Serial.println(F("Stop"));
    Stop();
  }

}
//-----------------------------------------



YouTube video:









延伸閱讀