×

用Stylophone控制伺服系统:音高检测

消耗积分:0 | 格式:zip | 大小:0.06 MB | 2023-07-11

分享资料个

描述

灵感

从一开始,我就知道我需要在我的项目中加入一个复古玩具。

第一步

收到我自己的留声机后,我拿起随附的公对公 3.5 毫米音频线(以及三节不随附的 AA 电池),用一只手将电线握在一起,另一只手将手写笔固定在键盘上,从而确定输出信号的范围.

 
pYYBAGOrt1aAfOhhAAAmloJtT5w600.png
2 环 TRS 插孔的引出线
 

由于留声机输出的是单声道信号,因此我使用笔尖 (T) 作为输入,使用套管 (S) 作为接地(顺便说一下,R​​ 代表环)。

基本的留声机有一个用于 3 种声音模式的开关、一个颤音开关、一个电源开关、一个音量旋钮和一个调谐旋钮。我建议在最大音量和无颤音下使用配置文件 1,下面的所有结果都使用这些设置。调准时,留声机的最低音符是 A (110Hz),最高音符是 E (329.63 Hz)。当留声机开启时(可以说是“预热”),留声机的输出最小电压范围为 -1.7 V 至 -100 mV,峰值电压范围为 1.7 V 至 3.5 V。自 TI MSP430G2553无法读取负电压,如果您移动参考电压,Uno 只能读取负电压,我决定使用 TI LM741 运算放大器构建增益和转换威廉希尔官方网站 。

威廉希尔官方网站

可以构建一个简单的威廉希尔官方网站 ,将 [-2, 2] V 转换为 [0, 4[ V 并应用增益以获得输出 [0, 3] V。

 
poYBAGOrt1yAWZv9AAHzYLekwfM587.png
非常相似的威廉希尔官方网站
 

可以使用来自 MSP430G2553 的 3.3V 电源和一个 10k 电位器来控制同相输入。图中的 R1 和 Rf 选择为 9.1 kOhm 和 12 kOhm,以实现 9.1k/12k ≅ 3/4 增益。还选择了较大的电阻器来限制电流量。考虑到这一点,我在 LTSpice(您可以在此处免费下载)中设计和仿真威廉希尔官方网站

 
poYBAGOrt3uAMtgAAAgtbzJiyx4272.png
模拟
 

幸运的是,TI 为他们的许多部件提供了 SPICE 模型,这样的精确仿真是可能的。(您可以在此处下载 LM741.mod )。模拟给出了从 [-2, 2] V 到 [0, 3.3] V 的预期偏移,所以是时候构建威廉希尔官方网站 了!

构建威廉希尔官方网站

威廉希尔官方网站 中使用的唯一部件是一个 9.1k 和 12k 欧姆电阻器、一个 0.1μ 法拉电容器、一个 10k 电位器、TI LM741、一个 3.5mm 音频插孔和一些电线。

 
poYBAGOrt7qAP-7WAA0CDvFvZS4126.jpg
带有电线的完整威廉希尔官方网站 ,可轻松连接到微处理器
 

焊接威廉希尔官方网站 后,使用 +12/-12 V 直流电源和 MSP430G2553 的 3.3 V 电压为其供电。从函数发生器提供 4 V 峰峰值的输入正弦波产生了我想要看到的结果。

 
pYYBAGOrt8SAMYgoAADrsytVvE8264.bmp
绿色为输入,黄色为输出
 

现在提供来自留声机的输入并调整电位器偏移...

 
poYBAGOrt82AAId1AADoKRPmsdQ758.bmp
低音
 

 

 
pYYBAGOrt9qAdD9BAAEmLD-jLqY788.bmp
高度关注
 

您会注意到,根据定义,高音 (E 329.63 Hz) 比低音 (A 110 Hz) 紧凑得多。这会产生采样问题,稍后将对此进行解释。

MSP430G2553(主控)

MSP4302553的作用是对音频信号进行采样,发送给arduino,根据接收到的信息,驱动舵机。ADC10 和定时器 A0 设置为以 8kHz 采样。

// Init ADC10
// Control Register 0
ADC10CTL0 = SREF_0; // Vr+ = Vcc and Vr- = Vss (default)
ADC10CTL0 += ADC10SHT_1; // 8 X ADC10CLKs
// ADC10SR: 2000 ksps (default)
// REFOUT: reference output off (default)
// REFBURST: continuous (default)
// MSC: single conversion mode (default)
// REFON: reference generator off (default)
ADC10CTL0 += ADC10ON; // ADC10 On
ADC10CTL0 += ADC10IE; // interrupt enabled
// Control Register 1
ADC10CTL1 = INCH_3; //input channel A3 (default)
ADC10CTL1 += SHS_0; // sample and hold source ADC10SC (default)
// ADC10DF: straight binary data format (default)
ADC10CTL1 += ADC10DIV_0; // Clock divider, try values and check noise
ADC10CTL1 += ADC10SSEL_0; // clock source ADC10SC
ADC10CTL1 += CONSEQ_0; // Single channel single conversion
// analog enable control register 0
ADC10AE0 = 8; // Enable A3 as ADC channel

引脚 1.4(任意)成为从机选择并初始化 SPI

// Initialize Port 1, P1.0 LED, P1.4 SS
P1SEL &= ~(BIT0 + BIT4); // P1.0 and P1.4 GPIO
P1SEL &= ~(BIT0 + BIT4); // P1.0 and P1.4 GPIO
P1REN &= ~(BIT0 + BIT4); // P1.0 and P1.4 Resistor disabled
P1DIR |= BIT0 + BIT4; // Set P1.0 and P1.4 to output direction
P1OUT &= BIT4; // Initially set P1.4/SS high
// Port 1 SPI pins, P1.5 SCLK, P1.6 MISO, P1.7 MOSI
P1SEL |= BIT5 + BIT7 + BIT6; // Secondary Peripheral Module Function for P1.5-1.7
P1SEL2 |= BIT5 + BIT7 + BIT6; // Secondary Peripheral Module Function for P1.5-1.7
// Polarity and SCLK for SPI
UCB0CTL0 = UCCKPH + UCCKPL; // first edge: data capture, following edge: data update , SCLK inactive state High
// Initialize SPI
UCB0CTL0 |= UCMSB + UCMST + UCSYNC + UCMODE_0; // MSB first, master, 3-pin, 8-bit synchronous
UCB0CTL1 = UCSSEL_2 + UCSWRST; // SMCLK, enable SW Reset
// bit rate: SMCLK/x=SCLK
// smclk same as arduino uno (16 Mhz)
UCB0BR0 = 32; // low byte, divide by
// 4 divider or greater, 8 and 16 causes issues, 32 seems okay
UCB0BR1 = 0; // same as 1 divider, high byte
UCB0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
IFG2 &= ~UCB0RXIE; // Clear RX interrupt flag in case it was set during init
IE2 |= UCB0RXIE; // Enable USCI0 RX interrupt

端口 2 设置为使用 TA1 和 TA2 引脚创建 PWM 信号来控制伺服系统。

// Init Port 2
// setup P2.1 with Timer1_A3.TA1 and P2.4 with Timer1_A3.TA2
P2DIR |= 0x12;
P2SEL |= 0x12;
P2SEL2 = 0;

退一步,让我们谈谈 SPI。

接口接口

它有很多内容,但归根结底是摩托罗拉在 1980 年代创建的通信接口规范,可用作双向通道。主机 (MSP430G2553) 和从机 (Uno) 需要共享四条主要线路:SCLK、MISO、MOSI 和 SS。SCLK(串行时钟)由主机生成,以便通信保持同步。MISO(Master In Slave Out)和 MOSI(Master Out Slave In 是街道的两侧,指示交通行驶的方向(有时称为 SOMI 和 SIMO)。SS(Slave Select)告诉从站何时应该收听master, 以及何时忽略它。这允许一个 master 拥有多个 slaves 而不会导致通信错误。配置 SPI 有不同的方法,但我选择的设置(如上面代码中指定的)看起来像这样。

 

 
pYYBAGOrt-KAMLpYAAKjTK04DJo860.png
 

该图可在 MSP430x2xx 系列用户指南的第 448 页找到。

Arduino Uno(奴隶)

Arduino 证明了一个使 SPI 通信变得容易的库,但只是为了让 Arduino 成为主人(去图)。

pinMode(MISO, OUTPUT);
// turn on SPI with interrupts, slave mode, msbit first
// clock idle when high, sample on falling edge of clock
SPCR = _BV(SPE) + _BV(SPIE) + _BV(CPOL) +_BV(CPHA);
/*
* SPIE - Enables the SPI interrupt when 1
* SPE - Enables the SPI when 1
* DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0
* MSTR - Sets the Arduino in master mode when 1, slave mode when 0
* CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0
* CPHA - Samples data on the falling edge of the data clock when 1, rising edge when 0
* SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz)
*
*/

设置只需要两行代码,但通信中的大部分困难在于让 Uno 作为奴隶合作。Uno 和 MSP430G2553 均以 16Mhz 运行,但 MSP430G2553 可以通过使用较小的分频器 (UCB0BRx) 设置波特率控制寄存器来更快地通过 SPI 进行通信。Uno 至少需要一个 4 分频器 (4 Mhz),但我发现偶尔会出现错误传输(0x1234 变为 0x3400),直到 32 分频器 (500 kHz)。我选择将 MSP430G2553 作为主器件的原因之一是 ADC 采用 10 位样本,而 Uno 只能采用 8 位样本。这里越多越好,因为它提供了更高的分辨率,这意味着 MSP430G2553 可能能够看到 1 mV 的差异,而 Uno 可能看不到。虽然这两个设备之间的 SPI 一次发送 8 位,可以简单地发送 2 组 8 位来获取 MSP430G2553 的 10 位值。唯一的问题是确保较慢的 Arduino 不会落后。

剩下的猫头鹰

最初的目的是在 Uno 上使用 FFT 或音调检测库,但这只会让事情变得更慢。最终,由于我知道来自留声机的信号的形状(每个周期一个上升沿和一个下降沿),所以我采用了一种更简单、更快速的方法。当前一个平均值小于阈值时,我只是等待最后 4 个样本的平均值大于阈值。这告诉我我触发了上升沿。然后我数了多久,直到再次发生这种情况并存储起来。然后我会简单地取最后 10 个计数/周期的中值来过滤掉任何异常值。这会瞬间发生,并允许伺服系统实时做出反应。

只需更改 MSP430G2553 上 PWM 信号的占空比即可移动伺服器。例如:

TA1CCR2 = 2100; TA1CCR1 = 2100;

最后的想法

我可能会尝试改进代码,以便稍后伺服在高频下更加稳定。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !

'+ '

'+ '

'+ ''+ '
'+ ''+ ''+ '
'+ ''+ '' ); $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code ==5){ $(pop_this).attr('href',"/login/index.html"); return false } if(data.code == 2){ //跳转到VIP升级页面 window.location.href="//m.obk20.com/vip/index?aid=" + webid return false } //是会员 if (data.code > 0) { $('body').append(htmlSetNormalDownload); var getWidth=$("#poplayer").width(); $("#poplayer").css("margin-left","-"+getWidth/2+"px"); $('#tips').html(data.msg) $('.download_confirm').click(function(){ $('#dialog').remove(); }) } else { var down_url = $('#vipdownload').attr('data-url'); isBindAnalysisForm(pop_this, down_url, 1) } }); }); //是否开通VIP $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code == 2 || data.code ==5){ //跳转到VIP升级页面 $('#vipdownload>span').text("开通VIP 免费下载") return false }else{ // 待续费 if(data.code == 3) { vipExpiredInfo.ifVipExpired = true vipExpiredInfo.vipExpiredDate = data.data.endoftime } $('#vipdownload .icon-vip-tips').remove() $('#vipdownload>span').text("VIP免积分下载") } }); }).on("click",".download_cancel",function(){ $('#dialog').remove(); }) var setWeixinShare={};//定义默认的微信分享信息,页面如果要自定义分享,直接更改此变量即可 if(window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){ var d={ title:'用Stylophone控制伺服系统:音高检测',//标题 desc:$('[name=description]').attr("content"), //描述 imgUrl:'https://'+location.host+'/static/images/ele-logo.png',// 分享图标,默认是logo link:'',//链接 type:'',// 分享类型,music、video或link,不填默认为link dataUrl:'',//如果type是music或video,则要提供数据链接,默认为空 success:'', // 用户确认分享后执行的回调函数 cancel:''// 用户取消分享后执行的回调函数 } setWeixinShare=$.extend(d,setWeixinShare); $.ajax({ url:"//www.obk20.com/app/wechat/index.php?s=Home/ShareConfig/index", data:"share_url="+encodeURIComponent(location.href)+"&format=jsonp&domain=m", type:'get', dataType:'jsonp', success:function(res){ if(res.status!="successed"){ return false; } $.getScript('https://res.wx.qq.com/open/js/jweixin-1.0.0.js',function(result,status){ if(status!="success"){ return false; } var getWxCfg=res.data; wx.config({ //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId:getWxCfg.appId, // 必填,公众号的唯一标识 timestamp:getWxCfg.timestamp, // 必填,生成签名的时间戳 nonceStr:getWxCfg.nonceStr, // 必填,生成签名的随机串 signature:getWxCfg.signature,// 必填,签名,见附录1 jsApiList:['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: setWeixinShare.title, // 分享标题 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 type: setWeixinShare.type, // 分享类型,music、video或link,不填默认为link dataUrl: setWeixinShare.dataUrl, // 如果type是music或video,则要提供数据链接,默认为空 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ”按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ空间”按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); }); }); } }); } function openX_ad(posterid, htmlid, width, height) { if ($(htmlid).length > 0) { var randomnumber = Math.random(); var now_url = encodeURIComponent(window.location.href); var ga = document.createElement('iframe'); ga.src = 'https://www1.elecfans.com/www/delivery/myafr.php?target=_blank&cb=' + randomnumber + '&zoneid=' + posterid+'&prefer='+now_url; ga.width = width; ga.height = height; ga.frameBorder = 0; ga.scrolling = 'no'; var s = $(htmlid).append(ga); } } openX_ad(828, '#berry-300', 300, 250);