去年在拆艾美特的风扇时特地留意了一下它的控制器,在网上查了一下型号是SM3015A。发现这个控制器功能非常强大,除了开关、定时、摇头、三档强度以外,还有自然风、睡眠模式等等,可惜我买的这一款面板上只有5个按钮,覆盖开关、定时、摇头、强度的功能,于是就打算增加几个按钮把自然风、睡眠模式引出来。后来突然发现它其实预留了红外遥控功能的,于是顺手又把它的红外配对遥控资料找了出来,决定修改设计不增加按钮改为增加红外遥控。前段时间买来红外接收头,顺便又用PIC的单片照着红外协议写了下测试程序。昨晚拆下它的控制面板,观察了一下红外接收头可以放在哪里,以及线路如何接入,今天中午花了大半个小时把线接好,就急急测试了一下发射端的程序,果然没有反应。
为什么说果然?因为但凡硬件设计,总是没有一遍成功的,总有这样那样的问题。于是晚上仔细的分析了一下:莫非硬件连接错误?大胆再拆开,冒着220V的危险测量了一下,红外接收头电压正确,面板控制功能正常,似乎不是硬件连接的问题。难道是软件的用户码不对?按道理是11啊,反正把00、01、10都试一下吧,还是不行。再细细读一遍协议,有地方说一帧长度16bit,有地方说一帧18bit,即4bit同步还是6bit同步的区别,分别再试,还是不行。最后落在逻辑表达上,逻辑表达的波形图有logic 0/logic 1/empty三个,咦?为什么还有empty?再看下面,用于同步的4/6 bit写的是empty啊。啊?我一直把同步当0来发。赶快修改代码,空4个bit出来,再试——不行,咬咬牙再改为6bit,只听bi-bi一声,风扇启动了,哈哈~~
赶紧试试自然风等模式,果然是有的,嘿嘿。下一步就等着吧遥控电路板焊好,再把低功耗代码完善就可以了~~
打电话告诉mm,结果她居然说要把这台电扇留给她用~~~why?好的都拿去了?不象话呀~
这两天在用PIC的单片写一个红外遥控的发射程序。先前以为比较简单的,仔细一算才发现不是那么容易。红外的调制信号是38KHz的,也就是大约26us一个周期,如果按50%占空比算就是每13us输出要变换一次。若打算用中断来实现,在片内4MHz振荡条件下,两次中断间隔也只有13个机器周期,肯定不够中断进入、退出、保护以及所必要的指令执行时间的。于是只能用代码置1/置0后延时来完成。这样设计的后果直接导致了在程序执行过程当中置1、置0的指令间隔需要完全固定,而且基带信号0/1之间的首38KHz置1脉冲间隔也必须一致并等于基带字节信号之间间隔。因此,程序当中大部分的分支都使用了类似这样的设计:
btfss senddata, 7 ; skip next instruction if bit7 of senddata is 1
goto sendb0 ; 1 cycle after btfss, this goto uses 2 cycles
nop ; 2 cycle after btfss, we need one nop to sync with another branch
call SendBit1
goto sendout
sendb0
call SendBit0 ; here the same timestamp with SendBit1
由于程序设计一定是分层实现的,即类似于:
SendFrame
call SendByte ( n byte times)
call SendBit ( 8 bit times )
call Send38K ( m times, depends on 38K cycles in one bit )
在完成以上动作过程中,要求两个Send38K之间的间隔必须是相同的,包括如下的几种情况:
1、Send38K->return->Send38K
2、Send38K->return->return->SendBit->Send38K
3、Send38K->return->return->return->SendByte->SendBit->Send38K
经过一番调整,我终于使得程序可以满足以上的要求,显然各个函数内部填充了不少的nop(空指令)以便让不同路径的调用最终在Send38K位置保持时间同步。当完成这些以后,发现遥控要求一次发射重复的四帧(共计8字节)。因为在调整中Frame内各个字节发送函数之间是按照2指令周期设计的,即用于将数据放入发送缓冲字节后调用发送函数,加上只需要重复四次,于是就偷了个懒,copy/paste写成如下的样子:
movf code1, w ; Frame1
movwf senddata
call Send
movf code2, w
movwf senddata
call Send
movf code1, w ; Frame2
movwf senddata
call Send
movf code2, w
movwf senddata
call Send
……
后来觉得这样做实在有弊端,一来有损俺编程的形象,二来可扩展性不强,若需要更多的Frame发射就只能copy/paste了。于是决定把这一小段改成循环的方式。第一次是这样写的:
clrf framecnt ; counter = 0
sendframeloop
movf code1, w
movwf senddata
call Send ; 6 cycles from the latter call out
movf code2, w
movwf senddata
nop
nop
nop
nop
call Send ; 6 cycles from previous call out
incf framecnt, f
btfss framecnt, 3 ; bit 3 of counter is set, that is 100B = 8 bytes -> we jump out
goto sendframeloop ; 2 cycles
sendframeout
这样写的结果就是两个Send函数之间的时间消耗由原来的 2cycles(2us)增加到了 6cycles(6us)。而Send函数中可以消化掉的上层函数时间增加只有 4cycles(4us),于是我决定平衡一下两个Send之间所干的事情,从第一个到第二个作了太多的nop,而第二个到第一个由是增加counter的值又是判断又跳转的,于是把计数增加调整到前面,这并不影响循环次数,于是变成了这样:
clrf framecnt
sendframeloop
movf code1, w
movwf senddata
call Send ; 5 cycles from the latter call out
movf code2, w
movwf senddata
incf framecnt, f
nop
nop
call Send ; 5 cycles from previous call out
btfss framecnt, 3
goto sendframeloop ; 2 cycles
sendframeout
虽然说一个微秒也是巨大的进步,可是离4us的限制还是差这么一点点。仔细观察从第一个Send到第二个Send除了装载数据2us以外就是计数增加用去1us,必须的操作是3us;而从第二个到第一个需要判断(1us),跳转(2us)加数据装载(2us),则必须的操作是5us,因此还需要下功夫
由于循环的存在,则从第二个Send到第一个Send一定会出现的操作是跳转(2us)和装载数据(2us),因此如果要限制两个Send之间只能用4us,则判断是否要跳出就要放在第一个Send和第二个Send之间,但btfss指令在出现skip操作是耗时将升为2us,而又没有指令可以替代,则会导致第一个Send到第二个Send之间出现数据装载(2us)、计数(1us)和判断skip(2us),又回到了5us的时代
柳暗花明的是计数操作可以通过替换Send函数内的nop指令在函数内完成以规避这样的问题,于是程序改为:
clrf framecnt
goto sendframe
sendframeloop
movf code2, w
movwf senddata
call Send ; 4 cycle to the later Send
btfsc framecnt, 6 ; we count framecnt in Send function, it counts 8 times per call
goto sendframeout
sendframe
movf code1, w ; 2 cycle after previous Send
movwf senddata
call Send ; 4 cycle after previous Send
goto sendframeloop ; 2 cycle
sendframeout
值得注意的是,进入循环是通过goto跳转到循环体中间进入的,而跳出循环体也是在中间,即两个Send中间的位置。由于Send顺序的变化,后一个Send发送的将是头一个字节,而前一个Send发送的是后一个字节的数据。
写完以后,自己感慨:这恐怕是到目前为止自己写的最bt的一段程序了。
在接触了PIC单片机以后,我一直认为PIC单片的IO引脚用起来是比较麻烦的,因为它与MCS-51系列的不同,从51系列转到PIC上的人一开始肯定会很不习惯。
PIC的IO引脚对于输入和输出功能是需要设定的,也就是设计者需要指定某个IO引脚到底是处于输入状态还是输出状态。于是它不像51,只需要把IO口输出一个1就可以马上读到IO外部的状态。当然,这样也带来一个好处,就是可以先输出0,然后设置为输入,此时的输出0并不会对外部的引脚造成影响,不像51,输出为0的情况下任何读入的数都是0了,呵呵。PIC的IO引脚这种特性可以在外部阻容的配合下对引脚状态进行检测(高电平、低电平或者悬空),详细的说明可以参看Microchip的《8-pin Flash Microcontrollers Tips ‘n Tricks》。51单片可不具备这样的功能。
就在我为PIC单片IO引脚复杂却带来好处感到欣慰时,最近又发现了这种方式的一个弊病。这个问题出现在对寄存器的“读-修改-写”操作上。对于普通的寄存器,这根本不是什么问题,偏偏是如果操作GPIO或者各种PORT寄存器,它的读实际上是直接读取的外部引脚,而不是GPIO或PORT寄存器。因此,如果某个IO端口你先设置为输出0,接着又再设置其为输入,如果你期望将来再次把它的功能设置为输出并保持原来的输出0的话,那么这个期间不能对这个(整个)GPIO/PORT有“读-修改-写”的操作,即便是操作同一个port的其他IO引脚都不行(例如用bsf/bcf指令)。因为,在那种情况下,原先设置为输出0的端口由于处于输入状态,从该引脚读到的数据可能是1,再被写回到整个PORT/GPIO寄存器,就把原来的寄存器当中输出0值给覆盖了。
这一点我是从PIC12F675的datasheet上对照GPIO的逻辑图看出来的,后来又在Microchip的《Complete Mid-Range Reference Manual》当中得到了确认。真是郁闷啊,为什么Microchip不学一下Atmel的51系列呢?Atmel的《8051 Microcontrollers Hardware Manual》当中对“Read-Modify-Write”指令特性当中涉及到端口的部分命令操作是对端口寄存器进行的读,而不是直接读引脚,其中的解释是“The reason that read-modify-write instructions are directed to the latch rather than the pin is to avoid a possible misinterpretation of the voltage level at the pin”。看看人家考虑得多周到,唉~~。
看来想利用切换PIC单片机IO引脚输入/输出功能来保留原来的输出值时,要特别小心了,或许需要用一个内存来保留我输出的数据,真是麻烦啊~~
不过另一方面看,自己能够在设计过程当中发现这样深层次的问题,总比把程序烧进去以后得不到结果瞎想的强多了。这也是收获吧,越来越觉得自己现在是想得多多,再去做了。
我说怎么最简单的c代码编译以后链接总是报这个错误呢:
Error – section ’sharebank’ can not fit the absolute section. Section ’sharebank’ start=0×00000070, length=0×00000010
原来sdcc生成.asm代码的时候用udata_ovr来声明全局临时变量了,并固定在0×70开始的位置。从它的字面意义和放置的位置来看,似乎是想把这些变量放置在不分区的内存位置。如果是对PIC12F675这样的片子,首先地址就错了,不可能是0×70开始,因为RAM地址在这个芯片上只有0×20+64B也就是0×60这么多而已,在0×70的位置没有定义。但是在调整以后还是不行。查MPASM的手册,原来udata_ovr在链接的时候使用的是在LKR当中定义的DATABANK区块,不分区(共享)内存在LKR当中用SHAREBANK定义,在.asm当中应该使用udata_shr来声明。
所以要么用udata_ovr定义,但是指定内存地址的时候不要指到SHAREBANK当中。或者改用udata_shr来声明。bug啊bug~~
sdcc(http://sdcc.sourceforge.net)是一款针对微控制器的小型ANSI C编译器。支持MCS 51/Motorlola HC08等系列的单片机。吸引我的地方在于它还支持Microchip的PIC系列的单片机,尤其是PIC14中档单片机。
本以为sdcc可以帮忙搞定一切,从.c一直到.hex的下载文件。用sdcc test.c之后得到的却是一堆的错误
仔细读manual,原来它本意是为MCS 51开发的,对PIC的支持还没有完全做完,不过已经可以把.c翻译成.asm了,至于从.asm到.hex的过程,可以用gputil或者用Microchip的MPLAB解决。而且对于PIC的支持,使用sdcc的时候要用sdcc -S -mpic14 -p12f675 test.c的形式翻译成.asm。只好依葫芦画瓢,果然得到asm的文件。照着manual当中用mpasmwin /o /q再对.asm进行编译,通过后用mplink链接却报share section出错。
反查.asm当中发现声明udata_ovr起始地址是0×70,显然不符合pic12f675只有64字节RAM的情况(加上0×20的SFR,也只到0×60),改为0×40后仍然报错,索性把后面起始地址的0×70去掉,解决。不过test.c只是很简单的例程,不涉及任何的SFR操作。当我加入对TRISIO操作时,mplink就开始抱怨_ANSEL找不到了
又回到manual看到需要将libsdcc.lib链接进来,可是照着命令敲始终不对,而且sdcclib和mplib都认不出libsdcc.lib的格式,怪哉。于是在sdcc的src code里面翻出pic12f675.c,然后把它编译了与test.o一同链接,通过。my god,难道sdcc提供的pic的lib是有问题的?改天把所有的lib重新编一次再说。
嗯,总算用c语言编了一个pic的程序,虽然只是试试看。下次好好把sdcc的环境整一整,最好能集成到MPLAB IDE里面去 ^_^
近日在对PIC12F675编程的时候,写可重定位代码,用udata定义变量。编译是正确的,但是链接的时候出现如下的错误:
Error – section ‘.udata’ can not fit the section. Section ‘.udata’ length=0×00000002
百思不得其解,换用PIC16F688的链接配置文件,则没有出现这样的问题。虽然16F688的内存资源比12F675多很多,但是程序当中使用的内存也只有2个字节,怎么样都不会出现不够的情况。仔细分析16F688和12F675的链接配置文件,涉及内存部分的有:
12F675:
DATABANK NAME=sfr0nobnk START=0×0 END=0×1F PROTECTED
DATABANK NAME=sfr1nobnk START=0×80 END=0×9F PROTECTED
SHAREBANK NAME=gpr0nobnk START=0×20 END=0×5F
SHAREBANK NAME=gpr0nobnk START=0xA0 END=0xDF
16F688:
DATABANK NAME=sfr0 START=0×0 END=0×1F PROTECTED
DATABANK NAME=sfr1 START=0×80 END=0×9F PROTECTED
DATABANK NAME=sfr2 START=0×100 END=0×11F PROTECTED
DATABANK NAME=sfr3 START=0×180 END=0×19F PROTECTED
DATABANK NAME=gpr0 START=0×20 END=0×6F
DATABANK NAME=gpr1 START=0xA0 END=0xEF
DATABANK NAME=gpr2 START=0×120 END=0×16F
SHAREBANK NAME=gprnobnk START=0×70 END=0×7F
SHAREBANK NAME=gprnobnk START=0xF0 END=0xFF
SHAREBANK NAME=gprnobnk START=0×170 END=0×17F
SHAREBANK NAME=gprnobnk START=0×1F0 END=0×1FF
可以发现16F688把内存分成了分区的部分(DATABANK)和不分区的部分(SHAREBANK,即共享区),而12F675只有不分区(SHAREBANK),全部是共享内存,不需要切换访问。于是,试试把12F675程序当中的变量定义到udata_shr当中,问题得到解决。也就是说因为PIC12F675的所有内存都可以不分区的访问,所以Microchip的MPLAB把12F675的内存全部定义成SHAREBANK,导致没有把内存定义到DATABANK。因此,发生了用udata定义变量链接出错的情况。
2006年7月1日
昨天在北京把电话的听筒元件买到了,今天回到宿舍第一件事情就是把烙铁拿出来,把听筒焊好就给mm打了电话。:-)
不过想起在北京买元件还是很好笑的。昨天的主要目的是去找3象限触发的高灵敏度双向可控硅,结果走了中发电子大厦和中海园电子城都没有找到
回来找安森申请样片,居然要我付邮费,只好作罢。在找双硅的过程中,就想到要买听筒喇叭,于是留了一个心眼。不过当时在想,找不到一模一样的就买一个大小差不多的8欧姆喇叭贴进听筒算了,呵呵。
不过对于喇叭这样的元器件,并不容易在卖电阻电容集成元件的市场里面找到。于是在四处搜寻一番以后渐渐开始找替代品的心理准备。功夫不负有心人,在中发的二楼碰到了一个卖喇叭的柜台,一眼看去呵呵8欧姆的喇叭一大排。从小到大,嗯,看看哪个尺寸合适
咦,旁边怎么有一路标有话筒,扬声器?还有大有小的……眼光往下扫,尺寸从小到大,最后一个的型号是SD-150B,好像我要找的就是SD-150!真是得来全不费功夫啊。
马上跟老板说,拿这个,嘿嘿。老板当然很高兴有生意上门,开口就问“要多少”
“……1个”,我都不好意思啊,可惜我买那么多干嘛?
“……”老板的脸色好像就晴转多云啊“2块”
其实一个扬声器嘛,多一点少一点差别不大,千万别不卖就行了,嘻嘻。于是赶紧掏了钱,喜滋滋的买走了,生怕老板反悔
回公司以后发现没有现场测量一下是不是好的,赶紧找来万用表,量了一下居然是130欧姆????why?难道不是8欧姆的。google是好东西,拿SD-150作为关键字搜索,找到参数是150正负20%。
看来凭经验去判断还是很危险的啊。幸亏没有买8欧姆的来充数。
04年王根走的时候,留了一个液晶模块给我。可惜之后我一直忙于各种事务,把它给忘记了。最近突然有要做点什么东西的想法,想起那个pickit 2还没有怎么发挥作用,于是就把它们都翻了出来准备玩玩。
自从学会了用pic的单片机以后,就很少再用51的单片机,毕竟pic的单片拿来做小东西玩是最方便的。首先pic的单片机从6pin、8pin到14pin、18pin、20pin到再往上的都很多,小型化的可选择面很广。其次,就功能来说,pic的单片机比较多,构造小系统很方便,例如有内置的振荡器,不需要外接晶体就可以干活,即便是8pin的器件也有内置AD、比较器等的器件等等。不过由于其引脚比较少,所以用来做用户接口或者是显示来说,就不太方便了。这也是为什么我想用LCD模块来显示的原因。显示模块的另外一个好处就在于我可以很容易看到内部的运转状况,呵呵,对于没有在线调试器的我来说,这太有用了。
首先翻出来的是液晶模块的说明,介绍了模块的接口,命令字等等,还有MCU软件的范例程序,可惜是51系列的,而且没有太多的注释。还好,基本上可以对照着接口、命令字等看清楚流程。照着范例程序翻译成pic的程序,连接好线,编程,上电……可是屏幕上并没有出现任何的字符。
这往往是最痛苦的一步,因为你根本不知道问题出现在哪里。于是回头对着手册一步一步看,发现没有做液晶初始化的动作。但是手册上的代码设置工作方式前只有一个循环了三次的代码,并注释道“此循环很重要,不要去掉”。我faint,哪有这种注释的,不说原因。于是琢磨是为什么?看其送入的命令字,是让液晶工作在8bit接口状态的命令,可是为什么要送3次呢?
Thinking…
估计是首先假设不清楚LCD模块工作在什么模式下,因此LCD应当有三种情况:
1、LCD处于8bit工作状态下,则送一次命令字即可生效
2、LCD处于4bit工作状态下,且处于等待命令的第一个4bit,此时送两次4bit共8bit的命令字可生效
3、LCD处于4bit工作状态下,且处于等待命令的第二个4bit,此时不管之前的第一个4bit是什么,先送一次4bit完成前一次命令,再送两次4bit共8bit的命令字也可生效。
所以,初始化循环做了3次,确保液晶工作于8bit接口状态。此时,状态就是确定的了,只需要按照所需要的接口状态再送命令字(例如切换到4bit模式,送一次4bit模式高4位,再分高4、低4两次送完整的4bit接口命令字即可)。
理解清楚这个以后,确保初始化动作完整,居然还没有任何反应。再硬着头皮检查,看看4bit接口逻辑是否正确……居然……发现高低4bit的发送顺序反了,oh my god!!!这种低级错误也能发生!faint,改好,再编程,yeah,屏幕上出现了01234567。咦?难道这个是一块8字符的?我一直以为是16字符的液晶。看程序,所有的内存都填了字符了,应该不会有错了,8字符就8字符吧,也够用了,呵呵。一看表,也已经到临晨两点了~~
这次用pic的单片,也发现有很多要注意的地方,也收获很大。
第一是首次用可重定位的代码来写程序,这样以后的移植性会好一些,但是也不可避免会带来一些问题,如冗余代码比较多。因为内存的分配由编译器来完成,这样你就不知道变量处于哪个bank当中,不得不频繁的使用banksel来处理,即便可能这条语句并没有改变当前的状态。更为关键的是这是一条伪指令,生成的是两条代码。当然后期也发现了一些诀窍,例如同一个udata里面定义的数据一定是放在同一个bank里面的,等等。
第二觉得51系列的IO口在IO上还是比pic的方便一些,pic的代码不少花费在切换端口指向上,虽然是双向口,但是需要明确指定是input还是output,更罗嗦的input/output寄存器和端口数据寄存器还不在一个bank上,又不得不作banksel操作,再加上具体的数据也不知道存储位置,选择存储器还要用一个banksel,于是实际代码冗余得就更厉害了;
第三是对pic的初始化部分还不算很熟悉,这次居然没有对IO进行初始化就用了,结果好几个端口处于模拟输入状态上,没有任何的输出反应,这还是在软仿真的时候发现的,晕倒。
总的来说,几个小时搞定了这个事情,又把3年前玩的pic给捡回来了,还是很高兴的~~~
上次从mm那里把她的坏电话拿到了武汉,说是要下次带过去的时候修好的。今天终于有时间来瞅瞅到底是什么问题。
首先确定表现,简单来说就是电话听筒没声音。不过上次在深圳通过观察确认拨号等部分是好的,于是下一步就要检查一下到底是电路方面的原因还是听筒话筒坏了。拆机,用万用表检测话筒线是好的,接着,把并线的另外一个电话拿起来,对着坏电话的话筒吹气,在好的电话里可以听到声音——话筒没问题,估计信号耦合部分也是好的。莫非是听筒扬声器坏了?
拆开听筒,用万用表测扬声器,200档电阻无穷大,一般来说都是8欧姆的扬声器的啊,换到20k档,还是没反应。不会这么简单的问题吧?找个东西来替换一下,四下张望,看到了桌上的小音箱。分了一个过来,用手按在扬声器的线上并联,拨号——小音箱里传出了小声的提示语言。果然是听筒扬声器坏了。等过两天去找个模样差不多的换了吧,呵呵。
昨天Van从美国回来,顺便也捎来了我的Microchip Pickit 2编程器。定的是套件,算上折扣,再加运费花掉了50美金,那个叫心疼啊。
虽然很久没有动手做硬件了,但是对单片机的兴趣还是很浓的。51系列是在课堂上和课外一块捣鼓出来的,而Microchip的PIC系列则完全是自己搞定的了。我还是很喜欢小引脚数的单片机的,最好是8pin的,就是要接显示的时候不方便,放宽一点20pin以下的都可以接受。PIC的单片还是很有意思的,很多功能都集成到片内了,精度要求不高的情况下甚至可以选择片内振荡器。外观上连接电源就可以使用,如果擦去标识,就很有隐蔽性了。记得以前研究的一个澜波洋的图文电视接收机就是硬把一个PIC的单片看成了I2C的存储器了,印象很深刻。
以前王根也帮忙做过一个并口的PIC编程器,从网上找来的软件。可惜对PIC12F675的操作不是那么顺利,加上USB渐渐成为趋势,心想还是弄一个USB接口的吧。8月份的时候在网上看见Pickit 2发布,就估摸着弄一个来玩玩,当时是打算只买编程器的,$35,加上邮费$9.5,就是$45这样,不到400人民币吧。想着在国内买说不定还便宜些。可惜问了一圈代理都没有听说过,pickit 1倒是有,18x人民币,差点就出手了,还是忍了一下。
上月找来一张20%折扣的Pickit 2套件优惠卷,算一下$50折扣以后只有$40,然后算上邮费也才$50,只比单买Pickit 2贵$5,也就是40人民币,还多一块电路板和一个片子,就让美国的同事帮忙定了。直到昨天Van从美国出差回来才拿到,不过还是很happy的。^_^
不过昨天最得意的倒是自己打算写一个Linux下的驱动,准备让这个东东在Linux下也可以用。我是头一次正儿八经的写一个驱动,还是USB的。先试了试流程,居然顺利的走通了。看样子这个硬件的东东倒是先让我在软件上长进了一把。不过mm昨天在电话里面已经勒令我赶快把她要的定时插座做出来,呼呼 ^_^
其实软件、硬件都是相通的,能在自己所学上挖掘对自己生活有帮助的东西还是很快乐的。