Today's the day

向软件大牛炫耀我会焊单片机,向硬件大牛炫耀我会写 Rails,向软硬件大牛炫耀我生物,向软硬件生物大牛炫耀我会折腾期货 -_-bbb

宅男必备 -- 脚踏键盘

嗯嗯,作为一名宅男,对着电脑吃饭是一种习惯。不过,可能跟别人不一样,我更加喜欢在吃饭的时候看静态的东西,比如长帖子、博文、漫画之类。

吃包子的时候还好,但是如果是吃面什么的话,看两眼就要腾出手来去翻页,那是相当不爽的。现在,终于做了这个,咩哈哈效果不错~

最贵部分就是这个脚踏板,要 60 大洋,淘宝上买的,这个已经是能找到的最不土的了……相比这个,其他部分的成本可以忽略不计了……

电路部分很简单,一块 M48,和一块旧的 PCB 板,上面引出接口刚好可以连接 USB 接口,和键盘引出口。那块 8 脚的小芯片,只是个 MOS 管,给 PCB 搭桥用,上面顺便有个 TVS 管防静电:

脚踏按钮是四线的,量一下确定分组,PCB 上正好有两个按钮的位置,接上去:

软件用 V-USB 模拟一下 USB 传输,做个 HID 设备,就完成了~

完成的连接图,再来根 USB 延长线,插到主机背后的 USB 接口中,请自行忽略地上的脏土 ^ ^b :

M48 4¥
二极管 x 2 0.3¥
USB 接口 0.2¥
MOS 管 0.7¥
其他元件 0.2¥

电路部分合计只要 5.4¥,可惜那个脚踏板太贵了……

默认的模式是 PageDown 和 PageUp,吃饭的时候看漫画用,此外也可以看 pdf 的时候做连续翻页,Protel 里面的放大和缩小也是 PageDown/Up,在鼠标拖动元件的过程中,通过脚踏板缩放图纸也很方便。

此外,也可以调为 Ctrl+C Ctrl+V 模式,解放左手

因为是 HID 设备,自然 Linux 下也能用,lyanry 出了个好主意,可以做个 Vim 模式,把脚踏调成 Esc 键,这样模式切换踩一下就可以了~

此外,做个老板键也不错~

拿来切换桌面也不错~

玩赛车游戏,设成油门和刹车键也不错~

……

用处很广阔啊~ 

目前的缺点:

1. 太贵,得去找个便宜点的脚踏板。

2. 脚感有点硬,而且声音咔哒咔哒的,夜里估计会很吵,得去找个软的静音踏板。

3. 切换模式得重新烧程序进去,下版搞个切换开关在上面。

单片机项目总结(一)-- 稳定性

以前只写桌面软件和 Web 程序,感觉一个稍有规模的软件要想做到良好的稳定性是要下一番功夫的,更不要说系统软件、内核模块、操作系统了这些了,因为他们确实太复杂了。

与此相对的,初涉单片机的时候,就感觉这东西很简单,也很难出故障,这个感觉和平常的经验也相符,毕竟,你什么时候看到过电子表死机?遥控器死机?刮胡刀死机?电梯死机?因为他们的逻辑就那么简单,根本就不可能死嘛……

不过现在才明白,如果你在实验室里做个电子玩具,那么以上是成立的,当你准备把这个玩具做成产品的时候,那么诸多问题就来了:你的产品也许不会像电 脑主机一样放在桌角就不动了,它也许会被放在 -20 度的户外,也许会被阳光晒到 50 度以上,也许会在湿度经常达到 80% 以上的南方夏天使用,也许会在干燥得一碰就能打出静电火花的北方冬天使用,也许会正好放在冰箱的压缩机旁边,也许会从桌上摔到地上再弹起来……

最初没有认识到这点,以为原型搞定了就算完工,结果之后在稳定性上花了大量的时间。不过也算了解了不少东西,特此记录一下~

1. Watchdog

看门狗,一直以来都知道有这么个东西,但是从来没有用过。第一印象是觉得这个模块的作用怎么这么弱智,不就是个定时的复位器吗,还要在程序中不断” 喂狗“,为啥要这么麻烦……现在明白了,因为你不知道你的程序会在什么地方跑飞,或者进入死循环,即便是设计的 100% 完美的代码,也可能在外部干扰、电压不稳的情况下,到错误的地方执行错误的代码,看门狗给让你的程序至少不会失去响应,大多数还有标志位让你能分辨出看门 狗重启从而做特殊处理。

无奈的是看门狗是要消耗电量的,对电池设备不是那么合适,不过如果你的设备不是要求一节纽扣电池就要运行两三年的话,那么还是把看门狗打开吧~

如果追求更低的功耗,和更健壮的稳定性,那么用专用的外部看门狗芯片也是不错的。除此之外也有不少芯片有看门狗这个附加功能,比如 HT1621,不用白不用~ :)

2.BOD

BOD 给我的第一感觉,也跟看门狗一样,不就是个低电压复位器嘛,认为在电压很波动的环境中用用还情有可原,一般情况下用不到。其实不然,因为电压过低是造成程 序混乱的一个主要原因,最低电压 1.8V 的处理器,在 1.0V 电压下也可以运行,但是内部已经混乱了。这次就遇到了用电池的设备在低温下莫名其妙跑飞的情况,最后才发现是电池在电量快耗尽的时候,在低温下电压会降得 很厉害,加上 BOD 就 OK 了。

BOD 也有同样的问题,就是一般也是要耗电的,因为为了比较电压,至少要维护一个电压基准。不过比如 MSP430 就有 zero-power BOD 技术做到零功耗的 BOD,新版采用 picoPower 技术的 AVR 处理器也有 Sleeping BOD 功能,同样可以达到睡眠状态下 BOD 无功耗,所以,也是不用白不用啦~ :D

3.好用的 EPPROM

对于一般功能的设备来说,有 Watchdog 和 BOD 就足够了,但是对于一些要维护状态信息和长期运行的设备,比如数据记录器、监控器,那么程序跑飞,就不能简单的重启了事,更重要的是要恢复现场,接着跑飞前的地方继续运行。

这就需要非易失存储器的帮助了,而 EPPROM 又是其中最好用的一个,100 万次的写入寿命,并且大部分处理器都内置了 EPPROM,又是不用白不用,即便没有,添加一块外置的 EPPROM,比如 24c02,也只需要几毛钱。

虽然 EPPROM 只有几千个字节的存储空间,却足够可以保存运行时的所有变量,配合 Watchdog 的重启中断,可以在处理器被重启前,把重要的运行时参数保存到 EPPROM 里去,重启之后再装载回来。

现在的项目中,把运行时的变量保存在一个 configs 数组中,然后定期把这个数组保存到 EPPROM 中,这样即便掉电重启,也可以快速恢复到最近的一个备份上~

4.插拔和对接

这里主要指运行中设备的插拔,和两个运行中设备的对接,因为在这些情况下会有一些需要注意的问题。

曾经做过一个设备,需要在 3V 和 5V 电源之间无缝切换,当 5V 电源插入的时候,就自动用 5V 电源,5V 拔下就自动用内部的 3V 电池。做好之后却发现一个奇怪的问题,3V 切换到 5V 没有任何问题,当把 5V 拔下来的时候,处理器重启了。一直以为是电源的切换速度不够快,5V 掉电却没有来得及切换到 3V 上去,但是后来发现原因不在这里,原因竟然出在电容上。因为 5V 是外接电源,所以习惯性的在电源入口处放了个大电容(下图 C11),这样当 5V 拔下的时候,这个电容就会瞬间放电,这个瞬时的高压就将处理器复位了,去掉这个大电容,一切正常了~

双系统对接,电压不匹配是主要问题,比如 3V 和 5V 系统之间通过 I2C 总线互联。原来以为,只要 3V 的设备可以耐受 5V 的高压,双方的 VOH 什么的也可以保证逻辑正确的话就可以直接对接了,在实验室里中也经常这样干,但是在产品中用却发现了诸多的问题。比如做 3V 和 5V 设备直接的互联,3V 的设备要插入到 5V 设备上的端口上去,大部分时候插入没有问题,但是少数时候,一插入 3V 的设备就立刻死机了。

原因在于大部分处理器的 IO 口都有钳位二极管的保护(上图中红色框内),让 IO 口的输入电压不会高于 VCC 也不会低于 GND。这也就是为什么3V 和 5V 的系统互联后,即便双方 VCC 没有连接,但是还会发现 3V 系统的 VCC 变成将近 5V 了(其实就是 5V 减一个二极管的压降);这也是为什么有的芯片,不接电压,直接给它的 IO 口输入驱动也能跑起来。因为电流通过 IO 口的钳位二极管流到 VCC 去了。

但是通常钳位二极管有最大电流的限制,3V 与 5V 之间 2V 的电压差,在设备内阻的配合下,会造成将近 10mA 的电流流过钳位二极管,大部分情况下这个电流已经太大了,会造成设备闭锁失去响应,应该控制在 uA 的等级之内才对。

简易的解决办法就是将双方的电压拉近,比如将 5V 的电压用稳压器降到 3.3V,那么和 3V 之间只有 0.3V 的电压差,造成的影响就会减少很多。

除此之外一个效果不错又省事的办法就是加限流电阻(下图中 Rser),这样可以有效的限制住电流大小,因为双方只要 VCC 不互联,那么电压做到严格一致是不可能的,有了限流电阻,就稳妥得多,并且这个电阻还可以有防止双方高频互扰的效果。负面效果就是电阻会拖慢边沿速度,高 速系统中不是很适用,但对于 I2C 这类只有几百 K 的应用,串联个 100 欧左右的电阻,是非常合适的。

更多的不同电压转换技巧,网上可以搜索到一份 Microchip 的 3V 5V 电压转换技巧手册,非常有参考性。

5. Delay and Try

单片机的固件中,经常看到类似的代码:

...
enable_adc();

while((!(ADCSRA & (1<<ADIF)));

read_adc_data();
...

中间用 while 来等待标志位置位,即便不用轮询而用中断方式,也会遇到类似的判断,比如在 I2C 传输中判断 I2C 中断标志位是否已经被清除、循环等待 ACK 信号之类的。

这种无限期的等待就有死循环的风险,如果硬件错误使得标志位置位失败,或者是 I2C 设备意外掉电,ACK 信号丢失,处理器就卡死在 while 这里一直等下去了。

虽说有看门狗可以在这种情况下复位,但是更好的方法是 Delay and Try,让程序稍微有点容错性:

unsigned char i = 10;
do{
    _delay_ms(2);
    i--;
}while((!(ADCSRA & (1<<ADIF))) && (i != 0));

这样,重试 10 次,在足够长的 20ms 内要是还是没有置位,就不再等了,当然后面的程序要有相应的处理机制。

同样的机制,也可以用在桌面软件中。一些特殊的操作,比如检测硬件、从硬件中读取数据、和驱动交互之类的不是每次都能成功,加个 Delay and Try,会稳定不少:

bool succes = false;
int retry = 10;

do
{
     succes = dwObj.FindDeviceFromID(VendorID, ProductID, ref path);
     if (succes)
        break;
     
     Thread.Sleep(200);

     retry -= 1;
} while (retry != 0);

return succes;

虽然方法很迂腐,但是很有效~ :)

慎用焊锡膏

焊东西都要加点助焊剂,一直以来用松香,便宜又好用,但是焊多脚的 SMD 贴片很不方便,后来换成日本产的一款焊油,相当好用。

但是这个日本焊油太贵了,而且含铅,前两天正好买元件,就顺便买了个国产的焊锡膏,两块钱一大桶,心想可用 n 年了。

结果,就被这桶焊锡膏搞了,晚上焊了几块板子,焊的时候感觉不错,甚至比那款日本的焊油感觉都好用,不过焊完了问题就来了。

先是工作电流应该只有几 uA 的电路,一上电就有三四 mA 的电流,而且还在不断变化,但是电路板上却找不到短路的地方,似乎一切正常。

于是开始一个一个拆元件,排除错误,哪里连焊了?芯片烧了?电容穿了?过孔连了?……

都不是。

最后我就震惊了,因为把所有的原件都拆除,就剩一张空板子,上电还有 1mA 左右的电流……

然后才怀疑到这个新焊锡膏上,测试了一下,果然如此。

拿了张新板子,板子上有一个 QFP 封装的 100pin 芯片,虽然引脚很密,但两个引脚肯定是绝缘的。

但是粘点焊锡膏,然后再拿烙铁烫烫刷刷,再测,竟然两个相邻的引脚有 50K 电阻!引脚相邻越远,电阻也就越大,不过即便很远的两个引脚,也有 2M 左右的电阻。

这下总算知道了为什么测不出短路,但是还有这么大的电流,因为这个焊锡膏,不是完全绝缘的!

可上焊锡膏的壳子上却印着”绝缘性良好“,网上查了一下,也确实有人提到焊锡膏腐蚀性很大,不适合电子焊接。

我也不知道是真不适合,还是国产货的质量太差,还是使用方法不对,也许是焊锡膏把板子腐蚀坏了吧。总之现在还是老老实实用回那个日本焊油和松香了了,虽然贵但是放心啊。

为了贪便宜耗费了一个晚上……

图为罪魁祸首。

USB 的 NRZI 编码

这两天继续看 USB 相关的内容,准备用纯软件实现一下 USB 设备传输,为将来的项目打好基础。

首先碰到的就是这个 NRZI 编码的问题了,基础太薄弱,看了一上午总算明白了大概。


首先,USB 的数据是串行发送的,就像 UART、I2C、SPI 等等,连续的01 信号只通过一根数据线发送给接受者。

但是因为发送者和接收者运行的频率不一样,信号的同步就是个问题,比如,接受者接收到了一个持续一段时间的低电平,无法得知这究竟是代表了 5 个 0 还是 1000 个 0。

一个解决办法,就是在传输数据信号的同时,附加一个时钟信号,用来同步两端的传输,接受者在时钟信号的辅助下对数据信号采样,就可以正确解析出发送的数据了,比如 I2C 就是这样做的,SDA 来传输数据,SCL 来传输同步时钟:

虽然这样解决了问题,但是却需要附加一根时钟信号线来传输时钟。有没有不需要附加的时钟信号,也能保持两端的同步呢?

有的,这就是 RZ 编码(Return-to-zero Code),也叫做归零编码。

在 RZ 编码中,正电平代表逻辑 1,负电平代表逻辑 0,并且,每传输完一位数据,信号返回到零电平,也就是说,信号线上会出现 3 种电平:正电平、负电平、零电平:

从图上就可以看出来,因为每位传输之后都要归零,所以接受者只要在信号归零后采样即可,这样就不在需要单独的时钟信号。实际上, RZ 编码就是相当于把时钟信号用归零编码在了数据之内。这样的信号也叫做自同步(self-clocking)信号。

这样虽然省了时钟数据线,但是还是有缺点的,因为在 RZ 编码中,大部分的数据带宽,都用来传输“归零”而浪费掉了。

那么,我们去掉这个归零步骤,NRZ 编码(Non-return-to-zero Code)就出现了,和 RZ 的区别就是 NRZ 是不需要归零的:

这样,浪费的带宽又回来了,不过又丧失宝贵的自同步特性了,貌似我们又回到了原点,其实这个问题也是可以解决的,不过待会儿再讲,先看看什么是 NRZI:

NRZI 编码(Non-Return-to-Zero Inverted Code)和 NRZ 的区别就是 NRZI 用信号的翻转代表一个逻辑,信号保持不变代表另外一个逻辑。

USB 传输的编码就是 NRZI 格式,在 USB 中,电平翻转代表逻辑 0,电平不变代表逻辑1:

翻转的信号本身可以作为一种通知机制,而且可以看到,即使把 NRZI 的波形完全翻转,所代表的数据序列还是一样的,对于像 USB 这种通过差分线来传输的信号尤其方便~

现在再回到那个同步问题:

的确,NRZ 和 NRZI 都没有自同步特性,但是可以用一些特殊的技巧解决。

比如,先发送一个同步头,内容是 0101010 的方波,让接受者通过这个同步头计算出发送者的频率,然后再用这个频率来采样之后的数据信号,就可以了。

在 USB 中,每个 USB 数据包,最开始都有个同步域(SYNC),这个域固定为 0000 0001,这个域通过 NRZI 编码之后,就是一串方波(复习下前面:NRZI 遇 0 翻转遇 1 不变),接受者可以用这个 SYNC 域来同步之后的数据信号。

此外,因为在 USB 的 NRZI 编码下,逻辑 0 会造成电平翻转,所以接受者在接受数据的同时,根据接收到的翻转信号不断调整同步频率,保证数据传输正确。

但是,这样还是会有一个问题,就是虽然接受者可以主动和发送者的频率匹配,但是两者之间总会有误差。

假如数据信号是 1000 个逻辑 1,经过 USB 的 NRZI 编码之后,就是很长一段没有变化的电平,在这种情况下,即使接受者的频率和发送者相差千分之一,就会造成把数据采样成 1001 个或者 999 个 1了。

USB 对这个问题的解决办法,就是强制插 0,也就是传说中的 bit-stuffing,如果要传输的数据中有 7 个连续的 1,发送前就会在第 6 个 1 后面强制插入一个 0,让发送的信号强制出现翻转,从而强制接受者进行频率调整。

接受者只要删除 6 个连续 1 之后的 0,就可以恢复原始的数据了。


关于 NRZI,大致就是如此了,不知道上面所说的有没有错误。其实这种编码的应用非常广泛,比如串口、CD/DVD 和通讯中,今天才见到还真是惭愧啊……

初步愿望达成

前两天折腾测试的时候还没发觉,今天才觉察到,不知不觉间,当初的一个愿望竟然达成了:

希望有一天,我能够自己制作一个有趣的硬件,再为他写一个 Linux 驱动程序,然后再通过 GUI 前端跑在桌面上~

就是和说的有点出入,是 Windows 的驱动,也是 Windows 的 GUI 前端。

不管怎么样,非常欣慰,因为当初认为能把这三个部分串联在一起,是一件遥不可及的事情。

虽说目前三个部分的水平都是半瓶醋,但是感觉能干的事情多了好多。

相信原来那种不知道写什么软件好的感觉,以后会越来越少。

曾经认为软件无所不能,目前发现他和其他学科结合而产生的事物更能激发自己的兴趣。

尤其期待和生物与机械的结合。前者虽然是专业,但是已基本还给老师,而且也无从下手;后者似乎也是有心无力。

貌似又是一件遥不可及的事情~

现在就不 YY 了,有空为目前的硬件写个 Linux 驱动好了~