Arduino 制作 HDMI Audio 测试工具

最近有客户报告:当插入 HDMI 从外接的显示器显示时,有一定概率出现显示正常但是 HDMI Audio 失效的情况。这个问题的概率不高,但是对客户影响严重。为了复制现象,需要不断插拔HDMI来验证,经常是插拔50次之后抓到一次现象,随之而来的是再来50次,更可怕的是据说某些修改之后会出现插拔100次才会出现一次现象。这对于我这样只愿意动脑不愿动手的人来说是一个极大的考验。

这种情况下,必须考虑用机器来实现自动化的测试。思路是淘宝上买一个带有遥控器的HDMI切换器,通过 Arduino 模拟遥控的操作来实现切换到 HDMI 上然后进行检测。于是,选择了下面这货14元+8元的运费。这个切换器有三个输入口,可以对应三台HDMI输出设备;有一个输出口,可以连接一台显示器

接下来要解决的问题如下:
1. 如何进行遥控?实现遥控之后如何保证切换成功(红外线遥控容易受到光线的影响)。
2. 如何检测当前是Audio 是否正常。
经过研究,第一个问题可以使用下面这个模块来解决:
红外解码模块 编码模块 红外无线通信 接收发射串口通信模块

因为这是发射和接收一体的模块,刚好可以用来解码HDMI切换器发射器的数据,解码之后发射同样的内容就能实现遥控的操作。遥控板上有3个按键,分别对应HDMI 的三个端口。经过实验遥控板按键发送数据如下:
HDMI IN1: 0xF1 0x80 0x7F 0x02 0xA1
HDMI IN2: 0xA1 0xF1 0x80 0x7F 0x04
HDMI IN3: 0xA1 0xF1 0x80 0x7F 0x06
接下来还有一个问题,如何保证切换正常。比如,当前是 HDMI1 输出,我切花发送切换HDMI2命令之后需要确认切换是否成功(红外遥控受到环境影响较大)。经过研究,可以使用切换器上的LED来判断,每一个输入端口都有一个对应的LED用来表示当前选中的输入端口。只需要读取LED电平即可得知。此外,我们再使用额外的LED来标记当前的输入 HDMI Port。
最终,硬件连接如下:


完整代码:

const int LED1=14;
const int LED2=16;
const int LED3=10;

void setup()
{
  pinMode(LED1,OUTPUT);
  pinMode(LED2,OUTPUT);
  pinMode(LED3,OUTPUT);
  digitalWrite(LED1,LOW);
  digitalWrite(LED2,LOW);
  digitalWrite(LED3,LOW);

  pinMode(A1,INPUT_PULLUP);
  pinMode(A2,INPUT_PULLUP);
  pinMode(A3,INPUT_PULLUP);
    
    Serial.begin(9600);
    Serial1.begin(9600);    
    

}

void loop()
{
  char  c=' ';
  int   retry;
    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if (c=='1') { //Command for Switch to HDMI1
            retry=3;
            while (retry>0) {
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x02);
              Serial1.write(0xA1);
              delay(200);
              if (analogRead(A1)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go1");
            }
        }
        if (c=='2') {//Command for Switch to HDMI2
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x04);
              delay(200);
              if (analogRead(A2)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go2");
            } 
        }
        if (c=='3') {//Command for Switch to HDMI3
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x06);
              delay(200);
              if (analogRead(A3)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go3");
              }
        } //if (c=='3')
    }
    
   if (analogRead(A1)<20) {digitalWrite(LED1,HIGH);}
   else {digitalWrite(LED1,LOW);}
   if (analogRead(A2)<20) {digitalWrite(LED2,HIGH);}
   else {digitalWrite(LED2,LOW);}
   if (analogRead(A3)<20) {digitalWrite(LED3,HIGH);}
   else {digitalWrite(LED3,LOW);}        
}

 

为了配合这个设备,我们需要编写上位机的代码, 上位机是运行在被测试机器上。主要的逻辑就是:发送切换命令切换到其他HDMI Port,之后再发送命令切换回来,然后检查音频是否正常,如果不正常就停止。如果正常就继续运行。软件界面如下:

代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;

using System.Runtime.InteropServices;

namespace WindowsFormsApplication8
{
    public partial class Form1 : Form
    {   //当前切换到 HDMI[x]
        int current=1;
        //切换次数
        int loops = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //如果当前测试还没有开始,那么开始测试,否则停止
            if (button1.Text == "Start") { button1.Text = "Stop"; }
            else { button1.Text = "Start"; timer1.Enabled = false;  return; }

            //检查选中的 HDMI 数量
            int counter = 0;
            for (int i= 0; i < 3; i++) {
                if (checkedListBox1.GetItemChecked(i)) { counter++; }
            }
            //如果只有1个HDMI 被选中,提示出错
            if (counter < 2) { System.Windows.Forms.MessageBox.Show("Please choose at least 2 HDMI"); return; }
            if (serialPort1.IsOpen) { serialPort1.Close(); }
            //打开选中的 COMx
            serialPort1.PortName = comboBox1.SelectedItem.ToString();
            serialPort1.Open();
            textBox1.AppendText("Open " + comboBox1.SelectedItem.ToString()+"\n");
            //1秒之内开始测试
            timer1.Interval = 1000;
            timer1.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //设置可以选择的串口号
            comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
            comboBox1.SelectedIndex = 0;
            for (int i = 0; i < checkedListBox1.Items.Count; i++) {
                checkedListBox1.SetItemChecked(i,true);
            }

            //输出当前的串口设备名称
            ManagementObjectSearcher searcher =  new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity");
            foreach (ManagementObject queryObj in searcher.Get())
            {
                if (queryObj["Caption"] != null)
                    if (queryObj["Caption"].ToString().Contains("(COM"))
                    {
                        //Console.WriteLine(queryObj["Caption"]);
                        textBox1.AppendText(queryObj["Caption"].ToString()+"\n");
                    }
            }
            searcher.Dispose();

        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen) {
                textBox1.AppendText("close " + comboBox1.SelectedItem.ToString());
                serialPort1.Close();
            };
        }


        // native calls
        [DllImport("winmm.dll")]
        public static extern int waveOutGetNumDevs();

        private void timer1_Tick(object sender, EventArgs e)
        {
            //如果当前是 HDMI2
            if (current == 2) {
                //检查设备是否有 Audio
                int inum = waveOutGetNumDevs();
                if (inum == int.Parse(maskedTextBox1.Text))  //发现错误
                {
                    textBox1.AppendText("Audio failed");
                    timer1.Enabled = false;
                    return;
                }
            }
            //20秒后触发下一次切换
            if (timer1.Interval == 1000) { timer1.Interval = 20000; }
            //尝试下一个HDMI
            current = (current + 1) % 3+1;
            //检查下一个是否被选中
            while (checkedListBox1.GetItemChecked(current-1)==false) {
                current = (current + 1) % 3+1;
            }
            if (current == 2) { loops++; }
            //切换到下一个可用的HDMI设备
            textBox1.AppendText("Switch to " + current.ToString()+"\n");
            textBox1.AppendText("Loops " + loops.ToString() + "\n");
            serialPort1.Write(current.ToString());
        }

        private void label1_Click(object sender, EventArgs e)
        {

        }
    }


}

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注