勇哥学了一段时间CP1H的指令后,突然有兴趣想做点东西。
突然想到我学易语言、C#、python后第一个想到的肯定是做点歌软件。(用来点播本地的MTV音乐带搜索功能的小软件,因为勇歌的爱好就是在户外唱歌)。
不过,点歌软件有大数据的处理、和互联网操作,cp1h是搞不了的,于是勇哥想到搞简单一点。
我只需要利用单片写一段红外遥控的代码,用串口发给plc,plc收到后按我约定好的指令格式向计算机上的一段音乐播放器程序发送,音乐播放程序收到的指令包括: 下一首,上一首,单曲循环,连续播放。
这个任务包括三个子项目:
PLC程序:其基本功能包括用slot1串口发送指令,用slot2串口接受单片机发来的数据
单片机程序:因为plc无法直接使用红外遥控,所以用51单片机实现这个功能,并用串口把红外遥控结果发送给PLC
电脑端程序:既然本项目是点歌,那么电脑端需要有一个播放器程序,它监听plc发送来指令来完成动作响应。
下面是实现三个项目的硬件与软件。
(欧姆龙 cp1h PLC)
(51单片机,红外线遥控器)
(电脑端的音乐播放软体)
PLC端的程序
(plc程序用到的地址的规划)
这个程序的功能是监视地址D1040,如果填入数值1,2,3,4就用lost1的串口发送出去。另一方面监视slot2的串口,如果被输入ascii码的字符1,2,3,4则填充D1040
程序第0步、第2步利用块程序,mov了一些字符串到plc内存地址里。共四个字符串,repeat,continue,up,donw 分别表示“单曲循环”,“连续播放”,“上一首”,“下一首”。
上位机电脑上的音乐播放软件会监听这些命令字符串而产生相应的动作。
第17,19,21,23步是按D1040的值为变址寄存器IR0赋值。在TXD指令进行串口发送时,其第一个参数是发送的内容,我们发送变址寄存器IR0的内容,它实际上是四个命令字符串其中一个的首地址。
第27步是清串品缓冲区,因为串口无论是发送,还是接收,在使用前和使用后都需要人工清掉串口缓存,否则A392.13和A392.06是不会置on的。这里我们执行清空的条件有两个,一是程序第一次跑的时候清空,二是在D1040被存入了大于0的命令字时清空。
第30步是串口发送命令字符串,以及发送后的清场工作。
发送的内容由变址寄存器IR0提供地址,控制字#100其中指定了由slot1的串口发送。
最后一个参数是要发送的字节数,由地址D1041提供。这里程序没有完善,我们应该由IR0的内容算出字符串的长度填充到D1041中去,而不是写一个固定的数字。(下载的源代码中会修正这个问题,而程序截图因为比较麻烦,勇哥就不管罗)
第37步的RXD指令接收外部输入,内容保存到D1050,命令字#200其中指定了由slot2的串口接收,最后一个参数的接收长度固定为2个字节,只只会接收ascII码的#31,#32,#33,#34,换成字符就是1,2,3,4,另外尾部还要加一个CR字符做为结束符。
第40步用于把ASCII码的,例如#3100, 转换为数值1,使用的方法是右移8位然后和常量#F进行位与运算。
如果你不明白这个是什么意思的话,请参见贴子: 欧姆龙cp1h常用指令学习(十三)数据移位指令
第46步判断用户输入是否>0 and <5,如是合理就置D1040为用户输入,这时候就会把命令字符串由slot1串口发送出去了。
串口收发的功能不光是需要程序,还需要有正确的设置,这方面如果有需要了解详细童鞋可以参考下面的贴子自己时行补脑。
欧姆龙cp1h常用指令学习(四)串口无协议读写指令TXD,RXD
PC端的程序
程序界面见上面的图片。
使用的时候,先搜索关键字,然后等出现列表后,勾选歌曲,然后play。
接下来你就可以使用红外线遥控就行四种命令的操作了。
这个程序只是个实验品,还有许多不完善的地方,比如没有实现随机播放,还有自动订时关机也没有测试过。
这个程序的原理是通过http操作酷狗音乐网站的搜索功能实现的,因此,调用的都是酷狗音乐的API。
所以,它是一款在线播放器,没联网没得玩。但是对于放过的歌曲,不联网也是可以重放的,因为有缓存。
注意:本程序是基于VS2017的,并且需要netMarketing类库的支持。
勇哥之前另写了一篇有关这个播放器的贴子,请参考如下:
原创C#源码,抖音热门音乐播放器
using netMarketing; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Speech.Synthesis; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Net; using WMPLib; using netMarketing.http; using netMarketing.diskFile; using sharClass; using netMarketing.automation; using System.Threading; namespace music { //www.skcircle.com by 勇哥(很想停止) //QQ:496103864 微信:abc496103864 public partial class Form1 : Form { [Serializable] public class musicStruc { public string SongName; public string FileHash; public string AlbumID; public string imgurl; public string lyrics; public string play_url; } [Serializable] public class configStruc { /// <summary> /// 搜索文本 /// </summary> public string searchtxt; /// <summary> /// 下载路径 /// </summary> public string savePath; /// <summary> /// 当前歌曲的图片 /// </summary> public Image img; /// <summary> /// 当前歌曲的歌曲名 /// </summary> public string songname; /// <summary> /// 当前歌曲的歌词 /// </summary> public string lyrics; /// <summary> /// 当前歌曲的播放路径 /// </summary> public string musicUrl; /// <summary> /// 播放模式 /// </summary> public int playModel; /// <summary> /// 搜索到的列表数量 /// </summary> public int searchListSum; /// <summary> /// 用户选择的音乐列表:hashID /// </summary> public List<string> selMusicList; /// <summary> /// 用户选择的音乐列表:歌曲名字 /// </summary> public List<string> selMusicListName; /// <summary> /// plc的com设定 /// </summary> public string plcComConfig; /// <summary> /// 单片机的com设定 /// </summary> public string scmComConfig; /// <summary> /// 单片机转发com设定 /// </summary> public string scmTransComConfig; } /// <summary> /// 播放模式: 1为顺序播放,2为随机播放,3为单区循环播放 /// </summary> private int playModel = 1; private int searchListSum = 320; private Stopwatch shutdownTime = new Stopwatch(); private long beginTime = 0; /// <summary> /// 用户选择的歌曲,保存hashId /// </summary> private List<string> selMusicList = new List<string>(); private List<string> selMusicListName = new List<string>(); /// <summary> /// 获取列表取得的全部搜索到的歌曲信息 /// </summary> private List<musicStruc> musicList = new List<musicStruc>(); private configStruc config = new configStruc(); private httpClass http = new httpClass(); private serialHelper plcCom; private serialHelper scmCom; private serialHelper scmTransCom; public Form1() { InitializeComponent(); } /// <summary> /// 初始化plc与单片机的串口 /// </summary> private void initCOM() { var ary1 = config.plcComConfig.Split(','); var ary2 = config.scmComConfig.Split(','); var ary3 = config.scmTransComConfig.Split(','); plcCom = scmCom =scmTransCom= null; if (ary1.Length == 5) { plcCom = new serialHelper(0, "接收plc控制命令的串口", new serialHelper.SerialParamType() { PortName = ary1[0], PortBaudRate = int.Parse(ary1[1]), PortDataBits = int.Parse(ary1[2]), PortParity = serialHelper.cToParity(ary1[4].ToUpper()), PortStopBits = serialHelper.cToStopBits(ary1[3].ToUpper()) } ); } if (ary2.Length == 5) { scmCom = new serialHelper(0, "播放器软件由这个串口取得单片机红外控制信号", new serialHelper.SerialParamType() { PortName = ary2[0], PortBaudRate = int.Parse(ary2[1]), PortDataBits = int.Parse(ary2[2]), PortParity = serialHelper.cToParity(ary2[4].ToUpper()), PortStopBits = serialHelper.cToStopBits(ary2[3].ToUpper()) } ); } if (ary3.Length == 5) { scmTransCom = new serialHelper(0, "播放器软件向这个串口转发单片机的红外遥控信号", new serialHelper.SerialParamType() { PortName = ary3[0], PortBaudRate = int.Parse(ary3[1]), PortDataBits = int.Parse(ary3[2]), PortParity = serialHelper.cToParity(ary3[4].ToUpper()), PortStopBits = serialHelper.cToStopBits(ary3[3].ToUpper()) } ); } if(plcCom!=null && scmCom!=null && scmTransCom!=null && plcCom.isInitOK && scmCom.isInitOK && scmTransCom.isInitOK) { ThreadPool.QueueUserWorkItem(new WaitCallback(plcThread), new object[] { null }); ThreadPool.QueueUserWorkItem(new WaitCallback(scmThread), new object[] { null }); } } private void plcThread(object obj) { string str = string.Empty; while (true) { try { if (plcCom.ReadTo(out str, plcCom.getEofStr(serialHelper.eofStrEnum.CR))) { outLabMsg(labPlc, str); switch (str) { case "repeat": cmdStop(); break; case "continue": cmdContinue(); break; case "up": cmdUp(); break; case "down": cmdDown(); break; } plcCom.clearComBuffer(); } } catch { outLabMsg(labPlc, ""); } ShowAlarm.Delay(50); } } private void scmThread(object obj) { var str = string.Empty; while (true) { if(scmCom.ReadTo(out str,scmCom.getEofStr(serialHelper.eofStrEnum.CR))) { var list1 = new List<string> { "1", "2", "3", "4" }; if (list1.IndexOf(str) >= 0) scmTransCom.Write(str); scmCom.clearComBuffer(); } ShowAlarm.Delay(50); } } /// <summary> /// 读取歌曲列表 /// </summary> /// <param name="keyword"></param> /// <param name="pageNums"></param> /// <param name="pagesize"></param> /// <returns></returns> private int readMusicList(string keyword,int pageNums=1,int pagesize=1) { var url = string.Format( "http://songsearch.kugou.com/song_search_v2?keyword={0}&page={1}&pagesize={2}&platform=WebFilter", httpExterend. UrlDecode(keyword), pageNums, pagesize); string jsonData = http.sendWebRequet(url); JObject jo = (JObject)JsonConvert.DeserializeObject(jsonData); musicList.Clear(); try { for (int i = 1; i < pagesize; i++) { var t = jo["data"]["lists"][i]; musicStruc data = new musicStruc(); data.SongName = t["SongName"].ToString(); data.FileHash = t["FileHash"].ToString(); data.AlbumID = t["AlbumID"].ToString(); if (data.FileHash.Length < 5 || data.AlbumID.Length < 2) continue; var res=getMusicInfoByHash(data.FileHash,data.AlbumID); data.imgurl = res.imgurl; data.lyrics = res.lyrics; data.play_url = res.play_url; musicList.Add(data); } } catch(Exception ex) { MessageBox.Show(ex.Message); } return musicList.Count; } private (string imgurl,string lyrics,string play_url) getMusicInfoByHash(string Hash,string AlbumID) { var url2 = string.Format( "http://www.kugou.com/yy/index.php?r=play/getdata&hash={0}&album_id={1}&=", Hash, AlbumID); string jsondata2 = http.sendWebRequet(url2); JObject jo = (JObject)JsonConvert.DeserializeObject(jsondata2); var t = jo["data"]; var imgurl = t["img"].ToString(); var lyrics = t["lyrics"].ToString(); var play_url = t["play_url"].ToString(); return (imgurl, lyrics, play_url); } private void setUIfromConfig(configStruc data) { label1.Text =data.songname; picMusicImg.Image =data.img; rtblyrics.Text =data.lyrics; axWindowsMediaPlayer1.URL =data.musicUrl; tbSearchtxt.Text = data.searchtxt; tbMusicDownPath.Text = data.savePath; tbPlcComConfig.Text = data.plcComConfig; tbScmComConfig.Text = data.scmComConfig; tbScmTransCom.Text = data.scmTransComConfig; cbxPlayModel.SelectedIndex = data.playModel; tbListCount.Text = data.searchListSum.ToString(); if (config.selMusicList == null) selMusicList = new List<string>(); else selMusicList = config.selMusicList; if (config.selMusicListName == null) selMusicListName = new List<string>(); else selMusicListName = config.selMusicListName; if (selMusicList != null) { for (int i = 0; i < clbMusicList.Items.Count; i++) { var ary1 = clbMusicList.Items[i].ToString().Split(','); if (ary1.Length == 2) { if (selMusicList.IndexOf(ary1[1]) >= 0) clbMusicList.SetItemChecked(i, true); else clbMusicList.SetItemChecked(i, false); } } } } private List<string> getMusicItems() { var list = new List<string>(); foreach(var m in musicList) { list.Add(m.SongName + "," + m.FileHash); } return list; } private void btnGetMusicList_Click(object sender, EventArgs e) { var txt = tbSearchtxt.Text; if (txt.Length >= 2) { config.searchtxt = txt; readMusicList(txt, 1, searchListSum); clbMusicList.Items.Clear(); clbMusicList.Items.AddRange(getMusicItems().ToArray()); } } private void Form1_Load(object sender, EventArgs e) { loadLastUiData(); shutdownTime.Restart(); splitMain.SplitterDistance = 400; } /// <summary> /// 反序列化程序数据 /// </summary> private void loadLastUiData() { try { var list1 =(List<musicStruc>) Serialize.FileDeSerialize(AppDomain.CurrentDomain.BaseDirectory + "data"); if (list1 != null) { musicList = list1; clbMusicList.Items.Clear(); clbMusicList.Items.AddRange(getMusicItems().ToArray()); } object list2 = Serialize.FileDeSerialize(AppDomain.CurrentDomain.BaseDirectory + "config"); if (list2 != null) { config = (configStruc)list2; setUIfromConfig(config); initCOM(); } } catch(Exception ex) { MessageBox.Show(ex.Message); } } /// <summary> /// 持久化保存程序数据 /// </summary> private void saveLastUidata() { if (musicList != null) { Serialize.FileSerialize(AppDomain.CurrentDomain.BaseDirectory + "data", musicList); config.savePath = tbMusicDownPath.Text; config.searchtxt = tbSearchtxt.Text; config.img = picMusicImg.Image; config.lyrics = rtblyrics.Text; config.musicUrl = axWindowsMediaPlayer1.URL; config.songname = label1.Text; config.playModel = cbxPlayModel.SelectedIndex; int.TryParse(tbListCount.Text,out config.searchListSum); config.plcComConfig = tbPlcComConfig.Text; config.scmComConfig = tbScmComConfig.Text; config.scmTransComConfig = tbScmTransCom.Text; config.selMusicList = selMusicList; config.selMusicListName = selMusicListName; Serialize.FileSerialize(AppDomain.CurrentDomain.BaseDirectory + "config", config); } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { saveLastUidata(); } private void btnDownSelectMusic_Click(object sender, EventArgs e) { var txt=tbMusicDownPath.Text; var selmusic = clbMusicList.SelectedItem.ToString(); var f1 = true; if(!Directory.Exists(txt)) { Directory.CreateDirectory(txt); } if(Directory.Exists(txt) && selmusic.Length>0 && f1) { config.savePath = txt; var hashid = selmusic.Split(',')[1]; foreach(var m in musicList) { if(m.FileHash==hashid) { var f2=http.Download(m.play_url,config.savePath+"\\"+config.songname+".mp3"); if (f2) MessageBox.Show(m.SongName + " 下载完成!"); else MessageBox.Show(m.SongName + " 下载失败!"); break; } } } } private void timer1_Tick(object sender, EventArgs e) { if(chkShutdown.Checked) { if(beginTime<1) { long t=0; long.TryParse(cbxShutdownTime.Text,out t); var t1=t*1000; if((shutdownTime.ElapsedMilliseconds- beginTime)>t1) { beginTime = 1; Process.Start(@"c:\windows\system32\shutdown.exe", "-s -t "+t*60); } } } getSelList(); if(null!=selMusicList && selMusicList.Count>0) { listUserSelMusic.Items.Clear(); listUserSelMusic.Items.AddRange(selMusicListName.ToArray()); config.selMusicList = selMusicList; config.selMusicListName = selMusicListName; } } private void getSelList() { if (null==selMusicList ) return; var list1 = new List<string>(); var list2 = new List<string>(); foreach (var m in clbMusicList.CheckedItems) { list1.Add(m.ToString().Split(',')[1]); list2.Add(m.ToString().Split(',')[0]); } //添加新的歌单 for(int i=0;i<list1.Count;i++) { if (selMusicList.IndexOf(list1[i]) < 0) { selMusicList.Add(list1[i]);selMusicListName.Add(list2[i]); } } //删除用户已经勾选的歌单 for(int i=selMusicList.Count-1;i>-1;i--) { if (list1.IndexOf(selMusicList[i]) < 0) { selMusicList.RemoveAt(i);selMusicListName.RemoveAt(i); } } } private void axWindowsMediaPlayer1_PlayStateChange(object sender, AxWMPLib._WMPOCXEvents_PlayStateChangeEvent e) { if (axWindowsMediaPlayer1.playState == WMPPlayState.wmppsPlaying) { var res = musicList.Find((s) => s.play_url.ToUpper() == axWindowsMediaPlayer1.currentPlaylist.Item[0].sourceURL.ToUpper()); picMusicImg.Image = http.DowloadCheckImg(res.imgurl); label1.Text = res.SongName; rtblyrics.Text = res.lyrics; } } private void button1_Click(object sender, EventArgs e) { if (selMusicList == null) return; axWindowsMediaPlayer1.currentPlaylist.clear(); IWMPMedia media; foreach (var m in musicList) { if (selMusicList.IndexOf(m.FileHash)>=0) { media = axWindowsMediaPlayer1.newMedia(m.play_url); //参数为歌曲路径 axWindowsMediaPlayer1.currentPlaylist.appendItem(media); } } if (axWindowsMediaPlayer1.currentPlaylist.count > 0) { axWindowsMediaPlayer1.Ctlcontrols.play(); } } private void cmdStop() { if (axWindowsMediaPlayer1.InvokeRequired) { axWindowsMediaPlayer1.Invoke(new Action(() => { axWindowsMediaPlayer1.Ctlcontrols.pause(); })); } else { axWindowsMediaPlayer1.Ctlcontrols.pause(); } } private void cmdContinue() { if (axWindowsMediaPlayer1.InvokeRequired) { axWindowsMediaPlayer1.Invoke(new Action(() => { axWindowsMediaPlayer1.Ctlcontrols.play(); })); } else { axWindowsMediaPlayer1.Ctlcontrols.play(); } } private void cmdUp() { if (axWindowsMediaPlayer1.InvokeRequired) { axWindowsMediaPlayer1.Invoke(new Action(() => { axWindowsMediaPlayer1.Ctlcontrols.next(); })); } else { axWindowsMediaPlayer1.Ctlcontrols.next(); } } private void cmdDown() { if (axWindowsMediaPlayer1.InvokeRequired) { axWindowsMediaPlayer1.Invoke(new Action(() => { axWindowsMediaPlayer1.Ctlcontrols.previous(); })); } else { axWindowsMediaPlayer1.Ctlcontrols.previous(); } } /// <summary> /// 设置lab文本 /// </summary> /// <param name="lab"></param> /// <param name="msg"></param> private void outLabMsg(Label lab, string msg) { if(lab.InvokeRequired) { lab.Invoke(new Action(() => { lab.Text = msg; } )); } else { lab.Text = msg; } } } }
单片机端的程序
勇哥就把手里的51单片机开发板的例程修改了一下,在红外线解码那里面,sendmsg()函数把红外线的控制字符由串口发送了出去。
勇哥实验了一下,4个控制功能的字符分别为:
m stop(停止播放)
f continue(继续播放)
? up(上一首)
0 down(下一首)
由于勇哥手头上的开发板用不了51仿真器,程序无法下断点调试,
所以虽然没有转为更形象的字符: 1,2,3,4 但是意义是一样的,就凑合着用吧。
串口的配置为: 4800, 8, 1 , N
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器 typedef unsigned int u16; //对数据类型进行声明定义 typedef unsigned char u8; sbit LSA=P2^2; sbit LSB=P2^3; sbit LSC=P2^4; sbit IRIN=P3^2; u8 IrValue[6]; u8 Time; int sendFlag=0; u8 DisplayData[8]; u8 code smgduan[17]={ 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0X76}; //0、1、2、3、4、5、6、7、8、9、A、b、C、d、E、F、H的显示码 /******************************************************************************* * 函 数 名 : delay * 函数功能 : 延时函数,i=1时,大约延时10us *******************************************************************************/ void delay(u16 i) { while(i--); } void sendmsg(u8 data1) { RI = 0;//清除接收中断标志位 SBUF=data1;//将接收到的数据放入到发送寄存器 while(!TI); //等待发送数据完成 TI=0; //清除发送完成标志位 } /******************************************************************************* * 函数名 :DigDisplay() * 函数功能 :数码管显示函数 * 输入 : 无 * 输出 : 无 *******************************************************************************/ void DigDisplay() { u8 i; for(i=0;i<3;i++) { switch(i) //位选,选择点亮的数码管, { case(0): LSA=0;LSB=0;LSC=0; break;//显示第0位 case(1): LSA=1;LSB=0;LSC=0; break;//显示第1位 case(2): LSA=0;LSB=1;LSC=0; break;//显示第2位 } P0=DisplayData[2-i];//发送数据 delay(100); //间隔一段时间扫描 P0=0x00;//消隐 } } /******************************************************************************* * 函数名 : IrInit() * 函数功能 : 初始化红外线接收 * 输入 : 无 * 输出 : 无 *******************************************************************************/ void IrInit() { //串口初始化 SCON=0X50; //设置为工作方式1 TMOD=0X20; //设置计数器工作方式2 PCON=0X80; //波特率加倍 TH1=0XF3; //计数器初始值设置,注意波特率是4800的 TL1=0XF3; ES=1; //打开接收中断 IT0=1;//下降沿触发 EX0=1;//打开中断0允许 TR1=1; //打开计数器 EA=1; //打开总中断 IRIN=1;//初始化端口 } /******************************************************************************* * 函 数 名 : main * 函数功能 : 主函数 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void main() { IrInit(); while(1) { DisplayData[0] = smgduan[IrValue[2]/16]; DisplayData[1] = smgduan[IrValue[2]%16]; DisplayData[2] = smgduan[16]; DigDisplay(); } } /******************************************************************************* * 函数名 : ReadIr() * 函数功能 : 读取红外数值的中断函数 * 输入 : 无 * 输出 : 无 *******************************************************************************/ void ReadIr() interrupt 0 { u8 j,k; u16 err; Time=0; delay(700); //7ms if(IRIN==0) //确认是否真的接收到正确的信号 { err=1000; //1000*10us=10ms,超过说明接收到错误的信号 /*当两个条件都为真是循环,如果有一个条件为假的时候跳出循环,免得程序出错的时 侯,程序死在这里*/ while((IRIN==0)&&(err>0)) //等待前面9ms的低电平过去 { delay(1); err--; } if(IRIN==1) //如果正确等到9ms低电平 { err=500; while((IRIN==1)&&(err>0)) //等待4.5ms的起始高电平过去 { delay(1); err--; } for(k=0;k<4;k++) //共有4组数据 { for(j=0;j<8;j++) //接收一组数据 { err=60; while((IRIN==0)&&(err>0))//等待信号前面的560us低电平过去 { delay(1); err--; } err=500; while((IRIN==1)&&(err>0)) //计算高电平的时间长度。 { delay(10); //0.1ms Time++; err--; if(Time>30) { return; } } IrValue[k]>>=1; //k表示第几组数据 if(Time>=8) //如果高电平出现大于565us,那么是1 { IrValue[k]|=0x80; } Time=0; //用完时间要重新赋值 } } sendmsg(smgduan[IrValue[2]%16]); } if(IrValue[2]!=~IrValue[3]) { return; } } }
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!
其它相关文章:
欧姆龙cp1h常用指令学习(十五)多重互锁指令MILH,MILC
欧姆龙cp1h常用指令学习(四)串口无协议读写指令TXD,RXD

