最近有客户报告:当插入 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) { } } }