作者归档:zamin

Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(4)

如果你看过本系列文章的上一篇关于如何从编译好的二进制固件里提取数据,那你距离最后的成功——通过bootloader给单片机下载固件——仅一步之遥了。

与ICD编程器利用芯片的PCG/PCD管脚下载程序不同,Microchip在其16位单片机芯片里实现了一整套指令/寄存器专门用于对自己的flash闪存进行读写。比如:

  • NVMADRU(高8位地址)和NVMADR(低16位地址)是记录flash程序空间地址的寄存器,他们所保存的24位地址指向用户所需要读/写/擦除固件程序的起始地址。
  • NVMCON寄存器中的NVMOP字节用于触发不同的flash空间操作,通过不同的设置执行比如批量擦除、页擦除、行擦除等。
  • TBLPAG寄存器用于保存”Write Latches”写锁存(下面有关于这个写锁存的讨论)24位地址中的高8位。对于dsPIC33E或者PIC24E的芯片,你只需要将“0xFA”赋值给这个TBLPAG寄存器,因为从下图中可以看出,“Write Latches”物理地址被硬实现从0xFA0000开始。
    BL4_Write_Latches_Address
  • TBLWTHTBLWTL指令用于将RAM中的数据“锁”入Write Latches中,然后才能执行最终的固件程序“烧”入闪存步骤。
  • 还有很多……

考虑到Microchip在其芯片上实现了“行编程”的功能,所以以“行”为单位(即每128个指令大小为单位)进行编程就显得比较方便而且快捷。“Write Latches”(写锁存)可以被理解为一个在Flash(高8位地址是0xFA)上面的临时数据存储器,用于临时存放这128个指令数据,等待最终的“烧制”过程。在行编程模式中,Microchip的disPIC33E系列似乎不支持直接将RAM中的数据直接写入用户闪存程序存储区,所有数据必须先进入RAM,然后从RAM中导入写锁存,最终再从写锁存写入目标地址的Flash中。下面是一段源代码展示了这一过程:

  1. 通过外围模块(peripheral,可以是UART、I2C、SPI、PMP、普通I/O等)接收固件二进制数据,并存入RAM:
            .bss
            buffer:  .space (#128*3)      ;在RAM内存中创建128×3字节大小的数组
            ;通过UART/SPI/I2C接受外面传进来的二进制代码,这里详细不列举
    
    
  2. 将RAM中的数据“锁”如写锁存“Write Latches”:
    
            mov       #128, W1      ;要“烧”128个指令,即一个“行”的大小
            mov       #0xFA, W0
            mov       W0, TBLPAG    ;上面提到的,Write Latches的地址总是从0xFA0000开始
            mov       #0, W2        ;Write Latches的低16位地址从0开始
            mov       #buffer, W3   ;上面创建的128×3大小的buffer的地址
    WRLA:   tblwth.b  [W3++], [W2]
            tblwtl.b  [W3++], [W2++]
            tblwtl.b  [W3++], [W2++]
            dec       W1, W1
            bra       nz, WRLA
    
    
  3. 执行“行”编程(row program):
            ;在开始最终的编程步骤之前,芯片必须知道这128个指令写到用户程序存储区的哪里。
            ;所以我么必须对此进行定义。
            ;假定W0寄存器里已经导入了地址的高8位
            mov       W0, NVMADRU    ;将这高8位导入NVMADRU寄存器
            ;假定W0现在导入的是地址的低16位
            mov       W0, NVMADR     ;现在将低16为导入NVMADR寄存器
            ;现在芯片知道写锁存里面128个指令应该写到flash的什么位置
            ;一些就绪就待最终的执行了!
            mov       #0x4002, W0
            mov       W0, NVMCON
            mov       #0x55, W0
            mov       W0, NVMKEY
            mov       #0xAA, W0
            mov       W0, NVMKEY
            bset      NVMCON, #WR
            nop
            nop
            ;等待写入操作完成。
    WWTF:   btsc    NVMCON, #WR
            bra     WWTF
    
    
  4. 完成了一个“行编程”(即128个指令),重复步骤1~3直到完成整个芯片的编程。

使用dsPIC33E系列单片机可复用IO(remappable)的注意事项

相比Microchip公司早期的16位单片机,dsPIC33E系列的一大新特色是可复用重映射的I/O引脚。这为电路板的设计提供了极大的灵活性,但同时也略微增加了编程的难度。下面就说一些使用心得:

  • 引脚若被标记成“RPn”则说明该管脚既可以被映射成输出也可以被映射成输入类型的I/O;而“RPIn”则只能被用于输入类型。在设计电路板的时候,需要特别留意,不要将“RPIn”用在了输出类型上。
  • 输出管脚的定义代码与输入类型不同。比如,如果将RPI16用于UART的接受器(Receiver)引脚,则程序里要这样定义:
    // U1ART RX connects to RPI16
    _U1RXR = 16;
    

    而如果要把RP118用在UART1的发送(transmitter)引脚上,你需要反过来定义:

    // RP118 connects to peripheral 0b00001
    _RP118R = 1; // 0b00001 stands for UART1
    

    我觉得这应该和芯片内部的Look-up table具体实现方式有关。

  • 单单定义了某一个管脚用在什么功能上还不够!请注意,尤其是当该引脚还和模拟口(通常是模数转换,ADC,标记为”ANx“)共用的时候,一定要特别设置将该模拟口功能关闭。不知为何原因Microchip对于这些引脚的默认功能为模拟口,除非特地设置ANSELx为零,不然无法使用其复用的数字电路功能。比如,如果希望使用下图中的3号管脚(同时也是AN29,RE5和RP85)将其映射到任何外设模块(如UART,DCI,SPI,I2C等等),必须在程序中声明:
    // ANSxy, x stands for Port A/B/C/D/E/..., y stands for number
    _ANSE5 = 0; // Enables digital port pin
    

    dspic33e_remappable_peripheral_io

    [2013年11月26日追加以下内容]
    拥有模拟功能的I/O口不仅仅局限于数模转换口,还包括运算放大器端口以及比较器端口,比如上图中RG6、RG7同时还具有C1IN3-、C1IN1-,在初始化后默认为模拟口。为了能使用其数字端口功能,则必须清空其相应的ANSxy寄存器(这个例子_ANSG6、_ANSG7)。我在一个使用DCI模块的项目里始终无法在其CSDI输入口上获得数据之后才注意到这个问题。因为CSDI被重映射到了一个比较器的输入口上,在没有清空ANSELx寄存器的情况下,该口上返回的值永远为零。一旦将其相应的ANSxy清空以后,数据马上就进入了DCI的接收模块里。

利用RC-6信号协议来同步遥控多台佳能T4i单反相机的视频摄影

让我们首先来看一段自制红外LED发射器来同步遥控多台佳能T4i摄像的视频画面(链接)。

佳能最新款入门级单反Rebel T4i设有一个方便实用的视频摄影按钮可以直接快速地开始/终止视频摄像功能。但是在专业的摄影人员、人工视觉和计算机图像从业人员眼里,这个“快捷键” 却不见得好用——尤其是你希望将多台T4i组合成一个相机阵列,并需要严格同步这些相机之间的开始/终止操作的时候,麻烦问题来了——你必须一台相机一台 相机地按过来,更不用提在按的过程中,还会导致相机略微移动。佳能更高端的旗舰型号1D Mark IV或者1DX有按钮重定义的功能,可以将摄像开始按钮定义到快门键上。这样可以通过快门线控进行多台同时操作。但是T4i没有这个重定义功能。

RC6_canon_eos_t4i_start_video_capture

如上图所示,标准的摄像操控方法:1.将开关拨到“Movie”档;2.按下摄像按钮即可。
不过也不是没有办法,细究一下T4i的手册,发现T4i可以使用佳能RC-6红外无线遥控器来进行遥控拍摄录像。

RC6_eos_t4i_remote_sensor1

佳能RC-6红外无线遥控器通过从顶端发出的红外线信号来远程操纵相机。如果其发出的信号被T4i相机所接受,相机则进行相应的操作。当然,必须对相机进行设置以便激活该遥控器,如下:

RC6_canon_t4i_drive_mode_menu

相机必须设置在中间位置的图标上才能使用遥控器。此时如果遥控器的开关设在“o”上,则按一下按钮拍一张静态照片;如果设在“2”上,则是开始/终止摄像。

RC6_canon_t4i_rc6

由于红外线遥控对方向距离十分敏感,光RC-6也解决不了同时操作多台相机的问题。就如我们平时所常用的电视机遥控器,你必须对准,而且在不太远的 情况下,才能控制电视机。佳能RC-6也一样有这个问题。除非你的相机阵列都面向同一方面,而且你可以靠得足够近的前提下,一个RC-6也许可以解决问 题。不然你不得不给每台T4i配置一个RC-6,而且还得想出个办法如何同时按下这些遥控器——怎么听上去还是直接按相机上的按钮得了~~~~~

有些人讨论布光纤的办法,让光纤把RC-6发出的红外信号传到每台相机的红外感应器前。我对此可信性表示怀疑。因为纤细的光纤是否能够传送亮度足够 的红外光强是一个问号。所以,下面就是我的终极办法:让我们找出RC-6到底发出的是何种协议的信号,然后给每个相机做一个小小的红外线LED,通过电路 板同时发出一样的信号!

那首先就是对佳能RC-6的反向工程了,所需要的是一个光电二极管(PD,photodiode,通常要比光电三极管响应速度快)和偏置电路。如果 网上搜一下“光电二极管电路”,你可以发现一堆像下面一样的电路图,很简单:一个PD串联一个10K欧姆的电阻,加上5伏电压即可。

RC6_3533b

在“+Vout”处接上示波器的探头,下面两张示波器截图就是RC-6发出的信号波形图:

rc6_pulse_zoom_in_pd_response RC6_o_configuration_pd_response

上图:RC-6在“2”位置上的PD波形图;下图:“o”位置。

一眼看上去,貌似很简单:两个0.5ms宽度的脉冲间隔5.8ms,就可以启动相机的摄像;间隔7.64ms,可以启动相机的拍照。这个波形在单片机上也不是很难实现:我使用了基于PIC32的RTM单片机平台、红外线LED和NMOS开关。电路简图如下:

RC6_customized_ir_led_for_t4i

很不幸……根本不起作用……可以确保完全按照上面波形图来实现。所以再次审视一下上面的波形图,发觉脉冲上方的“噪音”有点可疑,照理说信号干净的 话,脉冲应该是削平的……那就放大这个脉冲看看到底是什么,结果——发现脉冲上方其实是有规律的16个“坡”,每个间隔约30us。

rc6_pulse_zoom_in_pd_response

这些“坡”似乎下降地很慢,没到0电压就被拉回到了高电平。这时候突然想起来PD串联电阻的光敏电路的反应其实是很慢的,这也就解释了为什么示波器 上显示的是“半坡”而不是脉冲,因为电路放电慢于RC-6的脉冲速度。所以最后的结论就是RC-6在位置“2”上发出连续16个33.33KHz频率(约 30us间隔)的短脉冲,停顿5.32ms,然后再发16个同样的脉冲;如果是位置“o”,那么只要把5.32ms换成7.16ms即可。

实现了这个波形以后,T4i就乖乖工作了!

DIY为3D立体打印机制作加热工作平台(Heated Build Platform)

先来看一段这块加热平台完成以后的工作视频(链接)!这个Heated bed是为Type A Machine Series 1这款3D立体打印机设计的,但是相同的方法可以用于任何平台的打印机。

买了Type A Machine Series 1这台3D立体打印机有一段时间了。十分喜欢其巨大的“肚量”,可打印尺寸可以大到230mm x 230mm x 230mm(9英寸x9英寸x9英寸)。相比市场上的很多竞争对手来说,确实属于大块头了。当然更不用说其相当有竞争力的价格。不过话又说回来,这么多优 点都是有代价的:缺乏关键的加热工作平台是他的一大软肋。这往往导致用ABS材料的打印作品有严重的边缘翘起(corner lifting, curling, warping)现象,造成作品完全无法使用。

HPB_3d_print_corner_lifting_curling

图1,网上收集到的各种打印后边缘翘起的图片。

这里需要解释一下,为什么加热平台可以避免打印品边角翘起。主要是因为热胀冷缩。比如ABS塑料在打印的时候喷嘴温度高达到240摄氏度将耗材熔 化。拉丝成形后,ABS温度降至室温并开始变得十分坚硬。随着温度而来的就是体积的略微缩小,之所以说ABS形变要大于PLA,除了PLA的熔点 (150ºC)较低以外,ABS的热膨胀系数也要大于PLA。当整个ABS部件的体积大到一定程度时,微小的收缩累计起来造成较大的形变位移,使得最不牢 固的地方,既底部和打印台接触的地方,产生了翘起现象。所以,如果平台维持一定的高温,通常是100摄氏度甚至更高,那打印好的部件收缩程度要大大减少, 有利于在整个打印过程中维持原型。

3D打印的边缘翘起问题很长时间以来一直困扰着我。如果你经常使用PLA作为打印材料,或者用ABS打印小尺寸的作品,也许你不用担心这个问题。反 之如果像我一样,经常使用ABS打印大尺寸的部件,边缘翘起就会变成一个重要课题。我时不时需要打印体积大于100mm x 100mm x 100mm尺寸的部件,不巧PLA又不符合我的需要,所以为我的3D打印机添加一个加热工作平台(Heated Build Platform)看来是必须的了。

由于很幸运地可以使用数控机床,所以我直接使用1/8英寸(3.175毫米)厚的铝薄板。把板加工成打印平台之前,你需要知道大小尺寸等。生产 Type A Machine Series 1这家公司将几乎所有各项尺寸图公布了在网上,可恰恰不透露打印平台的数据。所以——只好自己量了,好在用不着过份精确。

HPB_Aluminum_Sheet_For_HBP

图2,铝薄板已切好合适的大小准备数控机床打孔。

HPB_Mill_Slot_On_Corner

图3,空有点打得难看,使用数控机床还不够熟练。孔边缘用counter sink削去了棱角以防划伤。

在我上一篇“DIY将家用小烤箱改装成电路板回流焊接炉(reflow oven)”里,我讨论了如何使用单片机,热偶式温度传感器以及继电器来控制家用小烤箱的温度。现在转变成要控制这块铝薄板的温度,其实是一回事情。大部分东西可以直接拿来用,需要变化的是,加热源和继电器。我使用TE的10欧姆16W金属电阻和Fairchild的N通道场效应管FQPF33N10L。这款N-Channel MOSFET只需要2V左右的栅极电压,因此可以直接用单片机的逻辑电平控制。

HPB_Metal_Clad_Resistor_HBP

图4,发热电阻用螺丝钉在了铝板背面。建议安装前贴一层导热胶布以便更好的热传导效果。

HPB_8_Metal_Clad_Resistor

图5,一共8个电阻以并联两组各4个串联的方式连接。因为这样需要的接线最少。4个串联的10欧姆/16瓦电阻需要至少48V的电压来驱动才能获得 最高的热功率输出。每个电阻需要1.2A的电流,既耗电14.4瓦。总共加起来需要48伏/至少2.4安开关电源。实际的测试显示铝薄板在室温下维持 100摄氏度的高温差不多耗电150瓦左右。另外,途中棕褐色的细线是热偶式温度传感器。用来向单片机通报实时温度值。

HPB_N_MOSFET_Switch

图6,N通道场效应管已经连到了单片机上。建议在单片机管脚后串联一个100欧姆的电阻,以避免震荡电流击毁单片机管脚。其余连线很简单:N MOSFET是地位开关,所以将48伏的电源输出端直接接到电阻负载上,电阻负载另一端接到MOS管漏极,源极接地。

HPB_Print_Flat_Base_No_Corner_Lifting

图7,好了,这里是一张在加热平台上完成的3D打印作品。可以看到四边平直地贴在平台上没有翘起。

HPB_Big_Print_Result

图8,这张图里是目前为止我打印过的最大尺寸的部件。耗时可谓“旷日持久”,不过结果很好。加热工作台确实很好地发挥了作用。另外你还可以看到,现在我可以打印“可剥离”的底座。下回我会讨论如果3D打印“可剥离”的底座。

Altium Designer生成gerber文件时的精度问题

最近向PCB工厂送出一块电路板设计,但是却被退回告知不符合最小走线间距要求。我使用该间PCB工厂已经有一段时间,对他们的各项指标十分 清楚。另外,我确定这块板子的设计肯定符合对方最小走线6mil的要求,因为我使用的Altium Designer的DRC(Design rule check)没有任何警告或者报错。为了进一步保证我的设计能满足对方的最小走线间距要求,我将最小间距提到了6.1mil,心想如果这样我的EDA软件 不报错,那板子再送过去应该肯定没有问题了吧。当然这个一提可是又花费了我不少功夫……

可是结果……还是说我的间距小于6mil,快把我逼疯了……

对方告诉我,我的设计里至少有50处最小走线间距违规,违规出显示实际间距为5.5mil或者5.7mil。这就让我起疑了,这些实际间距怎么才精 确到小数点后面一位呢?会不会gerber工程文件在输出的时候精度有问题?仔细检查了Altium Designer里各处gerber生成相关联的设置以后,终于真相大白:

PCB设计完成以后,你可以通过选择“File” -> “Fabrication Outputs” -> “Gerber files” 来调出CAM转换步骤的对话框,在这个对话框的第一个标签里,有一个Format(格式)的选项,就如其解释所说,选择“2:3”能精确到1mil(2表 示两位整数,3表示3位小数,1mil相当于一千分之一英尺,所以3位小数就是精确到0.001英尺,即1mil);“2:4”精确到0.1mil; “2:5”精确到0.01mil。显然,如果选择“2:3”作为输出格式,而设计精度要小于1mil,那小于1mil那部分小数位都被软件切割掉了。这就 意味着你的各种走线都被“移动”过了,况且你还不知道他们是怎么被切割的。这一移动毫无疑问就让我的设计超出了最小走线间距了。

Gerber_Setup

不过比较具有迷惑性的是,“Gerber Setup”并非是最终生成gerber工程文件的地方。这步只是将你的PCB设计转换成CAMtastic文件(CAM文件)。你可以把这个CAM过程 想象成一个成像过程,通过这个成像以后,你PCB设计中的各种netlist等关联信息都被移除,只剩下走线、丝印以及过孔等信息。所以你最后要做的,就 是将你的CAM逐层分解生成单独的gerber文件,这就是我们平时送到PCB工厂的文件。在生成之前,还有一个精度设置的地方,这既是我忽略并造成麻烦 的地方。

在CAMtastic窗口中,选择“Export”->“Gerber”出现下面的窗口:

Gerber_Export_Setting

要注意在“Settings”里面还有一个选择小数点位数的地方,这个才是最后要影响到你gerber精度的地方。确保你选择了和你自己PCB设计相吻合的精度。

选择高精度再次送出文件以后,没有再收到任何问题报告,电路板也顺利加工完毕。但是令人不解的是,为什么之前没有注意到这个高精度选项却也没有收到 任何PCB工厂的出错报告呢?后来才发觉,之前的PCB电路板的网格精度都没有低于1mil,而这次由于PCB密度很高,又不想提高PCB成本而选择更小 的走线/间距水平,所以将PCB的网格放到0.05mil,所以就出现了这样精度失真的问题。

无需网板的回流焊接电子元器件

首先让我们来看一段视频,演示了如何用回流焊接结束焊接电子元器件而无需网版(stencil)。

我在上篇文章中提到了如何DIY你自己的回流焊炉。 也许有人会纳闷光有焊炉有什么用,还得配上可以铺焊膏的网板才行。是的,网板(或者被称作模板)经常被认为是回流焊接过程中必不可少的工具。它不仅规定锡 膏该铺在哪儿,而且还规定了铺锡膏的量。当然,网板也并非很容易制造,通常需要特殊材料,并根据每块电路板的设计经行激光切割。这是为什么网板价格昂贵, 有时甚至贵过电路板,而且还只能用在一个电路板设计上。

With_Stencil
Without_stencil

图1,(从互联网上找来的图) 上图:用网板铺的锡膏效果;下图:不用网板的效果。

很明显,用了网板锡膏的铺设非常整洁精确。不用的话看上去一团糟,因为是手工抹上去的关系,有些地方直接被一大团锡膏盖住。如果说不整洁还能接受,因为毕竟这只是中间过程而已;那锡膏盖住多个焊盘会不会造成短路呢?

SL_Bridge_Across_Pads

图2,锡膏横铺在两个焊盘之上。

好在有了“表面张力”这个有利武器,即便像图1右图所示那样锡膏杂乱地涂抹在焊盘上,这块电路板仍然可以使用回流焊接技术完成元器件的焊接。我们都 知道,当焊膏被加热到一定温度以后,焊膏会融化而成“液体”状,并依照“表面张力”的约束而“流动”。涂抹在盘上之间(阻焊层)的锡膏会被抹在焊盘之上的 锡膏“吸”走。另外,表面张力还有助于元器件的对位。如果在前期放置芯片的时候没有十分精准地对上各个管脚,当锡膏熔化以后,液状的焊锡会将元器件“浮” 起并滑动到准确的位置上。最上方视频的结尾部分就演示了表面贴片的LED是如何被移动旋转入最终的位置。

所以,只要你有一台回流焊炉,你就可以立即开始进行焊接,用不着网板甚至是打片机。

如何用Slic3r生成3D打印的可剥离支撑底座

首先来看一段如何将3D打印完成后的辅助支撑底座剥离的视频。如果你觉得很需要这个可剥离的底座来实现完美平直的打印品底面,那就请按照我后面的步骤操作即可。

3D打印作品时,在打印平台上先打出一个支撑底座是一个很实用的操作,其优点显而易见:底座将作品抬离平台,有助于保持作品底面的平整,平台上任何 痕迹不平整等问题不会影响到作品;因热胀冷缩而产生的边角翘起问题只会影响底座,而不会影响到在底座上方的打印品。但是用相同材料相同机器打印出来的底座 如何能很容易的和上方的作品剥离,是一个令人头痛的课题:一方面你希望底座可以牢固地支撑起上方的作品,不会产生作品边角翘起现象;另一方面,你又不希望 底座和作品粘连得太牢固而无法分离。那有没有办法同时实现这个貌似相悖的想法呢?答案是肯定的,至少有一个现成的产品已经做到了这点:UP!3D打印机,其最出名的就是可以打印可剥离的底座。

UP!3D打印机虽好,可它不属于RepRap开源社区的一分子,也就意味着它如何实现可剥离底座的算法是不公开的。我仔细检查了UP!3D这款打印机,除了一般打印机都具备的零部件以外(喷嘴,XYZ三维轴承,加热打印平台),它并没有什么特别之处。那看来其神秘之处就藏在软件里了。最近欣喜地发现开源社区的层切软件Slic3r在新版0.9.9里面增了一个“Raft”功能,我决定用我的Type A Machine Series 1来试一试看看这个Raft是不是我想要的可以剥离的底座。如果我成功了,你可以同样地复制在你的打印机上。

3D_Print_Raft_CN

图1,用Repetier-Host可视化出来带底座打印模型(左),无底座打印模型(右)以及用UP!3D打印出来的带底座作品。

看到最左边的图片就开始令我兴奋不已了,至少这是Slic3r破天荒第一次生成了非常类似于UP!3D的支撑底座!接下来的问题就是,这个辅助底座是不是很容易剥离?那就用我的机器打打看了!

Raft_No_Removable_CN

图2,结果有点令人沮丧。我的打印模型背面有一大块属于底座的部分残留着,非常牢固,无法剥离。

那让我们看一看这个新的Slic3r生成的底座部分g-code到底是什么样的。我还是推荐使用Repetier-Host来 可视化,它可以帮助你将g-code一层一层分开显示,十分有助于全面了解打印整个过程。我只做了一个十分简单的矩形体,大小为44.45毫米 x 44.45毫米 x 5.08毫米,打印层间距设为0.2毫米,支撑底座高度设为49层。下图是Repetier-Host上看到的结果(使用了photoshop处理了截 图)。

3D_Print_Gcode_Analysis_CN

图3,如图所示,最下方为一层底层,然后是49层底座,49层中最上方的3层被设置为实心层,Slic3r里称之为接触层(interface layer)。可以看到,这个接触层的最上面一层距离下面一层的距离只有0.129毫米,而非所设置的0.2毫米。另外,在这个接触层和模型之间,有一层 额外的分割层(separator layer),这是由Slic3r自动生成的,距离下方的接触层顶层0.321毫米。看起来Slic3r是想将底座最顶部的3层实心接触层贴地很近地打印 从而做出一个十分坚固的底座,而将上方的分割层开始的模型分开一定距离打印,从而实现“可剥离”。

这确实是不错的想法!因为3D打印最基本的原理就是通过将塑料耗材(ABS或者PLA等)高温融解后相互粘织在一起而形成各种形状。当两层之间的距 离相当近的话,他们之间的粘连度也就更高;反之亦然。可是Slic3r在这个思想指导下所生成的gcode为什么就不能完全实现该有的效果呢?至少图2里 面只有一部分底座是容易剥离的,其他的十分牢固地黏在打印作品上。所以我决定将分割层和底座的距离再次拉开0.1毫米试试。同时,考虑到耗材的粘连度和喷 嘴融解温度也是有关的,如果用较低的温度(比如200度而不是平时的235度)打印分割层,则有助于减小分割层和底座的粘连性。可惜Slic3r的 0.9.9版还不支持我的想法,我只能自己写了代码自动修改Slic3r生成的gcode。我使用下面的gcode来调控温度:

M109 S200 ; 设置新的温度,并停下等待直到达到该温度。
M109 S235

实际使用来看,喷嘴需要差不多一分钟的时间在两个温度之间升降。我同时还观察到,喷嘴在等待温度变化的过程中,有一定量的耗材会滴出喷嘴,所以我插入了一段代码让喷嘴在等待之前先升高一定的距离,这样有助于清理而不至于让他们粘在底座上。

Increase_Distance_Raft_Model_Change_Nozzle_Temperature_CN

图4,左边显示了被扩大的缝隙,可以在Repetier-Host里看到。右图青色的线条是喷嘴行走的轨迹。下面这段视频展示了我自己编写插入的代码是如何在Type A Machine Series 1上工作的。

最后来看看我的可剥离底座实物图!

Peel-able_3D_Print_Raft

图5,现在的底座可以剥离了!希望有一天Slic3r可以将我的想法实现到他们的新版本中!

DIY将家用小烤箱改装成电路板回流焊接炉(reflow oven)含视频

先来一段回流焊接炉最终成品工作时的视频(链接):

尽管我可以很熟练地手工焊接小到只有0.5毫米间距的QFN封装芯片(看我的文章视频), 但是越来越多出现在市场上的新产品芯片已经变得实在无法手焊了。手上的各个项目已经发展到了不得不要丢我的电烙铁的阶段。看来到是时候给我的工作台添加一 台电路板回流焊炉了(reflow oven)。市场上已经有不少产品可供选择,从专业级别到面向业余爱好者的都有。但是我还是决定要自己动手改装一台,肯定很有趣味,更不说还能省下不少银 子。

打造一台电路板回流焊炉,你需要四种主要零件:1)加热设备;2)温度传感器;3)控制器;以及4)继电器。不过这些东西对于电子工程师来说应该不是什么大问题。

4_Elements

下面是针对上面零件所作的选择:

  1. 可放下12英寸皮萨饼的热风电烤箱。选购家用烤箱首要注意的地方是功率。必须要能够在短时间内升温至最高260摄氏度。通常功率1千瓦的电烤箱可 以做到这点。接下来,带热风功能必须的,因为烤箱自带的风扇可以将烤箱各个角落的温度通过空气循环来均衡升温。加热棒应该是两条位于烤箱顶部,两条位于底 部。试验结果告诉我烤箱容量可以不用这么大(如果你需要焊接大面积电路板那另当别论),因为过大的烤箱空间导致加热缓慢,这在后面我会提到。比如我购买的 这个烤箱无法做到在90秒内升温至150摄氏度。如果有机会我会选购一个小点的,不过总体来说工作情况令人满意。最后,我强烈建议选择手动控制的电烤箱。 那些看似十分高级的数码控制款,不仅价格上比较贵,而且对后面的改装会提出不少难度。
  2. K型热电偶式(Thermocouple)温度传感器,我选用的是TPI的GK11M,以及热电偶调节器模数转换芯片MAX6675。 需要确认所选购的热电偶可以工作在至少260摄氏度的温度下。GK11M这款K型热电偶的工作范围是-40~950°F (-40~510°C),足够了。MAX6675带有SPI结构,可以很方便的和单片机等相互通信。这款芯片有些小贵,因为它连数模转换都集成了,如果不 嫌麻烦愿意自己在单片机上实现数模转换(ADC),则可以选购模拟输出的芯片,相对会便宜些。
  3. 控制器方面,我使用我自己开发的RTM平台,主芯片是一块Microchip PIC32MX795F512L的32位单片机,系统时钟可以跑到80MHz,带有一个USB2.0口,这样可以和PC很好的结合起来,所需要的编程量也很低。
  4. 继电器我选用Crydom的PF240D25固态继电器。由于我需要开关交流电,所以至少可以负载1000瓦的大功率继电器是必须的。考虑到我准备使用脉冲宽度调制(PWM)的方式来控制烤箱,所以推荐使用固态而不是传统的电磁式。这款继电器还可以直接使用单片机的控制信号,十分方便。

好了,所有零部件到位,是时候动手将电烤箱改装成回流焊接炉了!

Toaster_Oven

上图1绿色圆圈所示,这台烤箱带有三个控制旋钮,分别是温度控制、功能选择和定时。我需要拆掉最上方的温度控制器(实际上是一个利用两种不同热膨胀 系数的双金属条,bimetallic strip),转而用我自己的单片机电路来控制温度。“功能选择”也不需要,但放着也不碍事,如果有低温项目需要,还可以切换到只有两条加热棒工作的模 式。“定时器”也不需要,因为我的电路板会控制时间,不过考虑到安全因素,还是建议留着,至少万一忘记切断电源的情况下,定时器最终可以帮我关掉烤箱。

Toaster_Oven_Rewiring

图2. 这是烤箱控制面板内测的布局。注意图中中间上方有一个白色胶布包裹的装置是热风风扇的变压器线圈。这是整个回流焊接炉的核心器件之一,保证电路板各处均匀 加热。你可以看到,我已经将最上面的温度控制器断开。另外一个小改动是将热风风扇跳过功能按钮,这样只要该烤箱上电,风扇就一直工作。

Toaster_Oven_Rewiring_2

这张图3展示了一个已经完成重新布线的半成品焊接炉。只要图中蓝色的插头连接,烤箱就开始加热。有烤箱内部没有其他任何的电源相关部件,所以这根电 线里跑的就是交流电,操作的时候需要注意安全!接下来就很容易了,把这两个插头连到继电器上,然后用软件来控制这个继电器的闭合/断开。

Solid_Sate_Relay

图4. 参照这个继电器背部的说明,4个管脚的连接十分明了。1/2管脚连到上图的蓝色插头,无需考虑极性的正反。3/4管脚连到单片机的输出端,管脚3也不需要串联限流电阻,4是接地。需要注意的是,该固态继电器在使用是会产生大量的热,需要降温设备。

Everything_Connected

图5. 所有连线完毕。我的电路板回流焊接炉准备就绪了!等等……我还需要一个降温风扇!

SSR_W_Fan

图6. 降温风扇也准备好了。我在单片机里实现了一套PID控制程序来实现所要求的温度走势。网上到处都可以找到各种不同的温度要求,通常一个加热焊接过程被分成 三个步骤:1)预加热阶段(Pre-heat zone);2)均热处理阶段(Soaking zone);以及3)回流加热阶段。

  1. 预加热要求在不超过2分钟的时间内把温度从室温提升到150摄氏度。150摄氏度低于锡膏的熔点,只是确保电路板各处温度升带这一数值。
  2. 均热处理要求在接下来的2分钟内温度升到183摄氏度并保持在这一温度上。在这阶段内,锡膏里的助焊剂被激活,确保各种辅助液体被挥发掉。183度任然略低锡膏的熔点,但已十分接近,我以后的文章会提高,这是的锡膏已经开始液化。
  3. 最终,回流处理阶段阶段要求温度以最快速度上升到250摄氏度,高于锡膏的熔点,锡膏充分附着于焊盘和元器件管脚上完成焊接。

在不断的尝试以后,我最终将PID各参数调整到最佳值使其能实现上面的三阶段要求。由于我的RTM单片机系统可以方便的实现USB通信,我在PC上简单地将下面的参数输入系统:

秒   摄氏度
120, 150
240, 183
300, 250

第一列数据是以秒为单位的时间值,第二列是在该时间内要达到的摄氏度温度值。下面是我通过单片机系统反馈得到的实时温度走势图。

Temperature_Profile

你可以看到,就如我之前所提到的,由于我的烤箱过大,导致箱内升温缓慢,所以未能在规定时间内达到要求温度。而且回流温度也低于250摄氏度。不过 令人惊喜的是,尽管有这些问题,但是自动焊接结果十分完美。下面是一些使用该回流焊接炉的视频和图片。随着更多的电路板被我烘焙,我会贴出更多的结果。

回流焊接好的电路板:

PCB_Reflow_Rebel_Z

1.7mm x 1.3mm大小的飞利浦Rebel Z系列彩色大功率LED。
PCB_Reflow_Rebel_Z_ES_effect

Rebel Z系列彩色LED以及3mm x 4mm Rebel系列白色LED。Rebel Z系列彩色LED提供8种不同的颜色,分别是:绿、蓝、红、桔红、深蓝、琥珀黄、品蓝以及青。

以下文字添加于2014年6月21日:

距改装这个小烤箱已经有一年多的时间了。这个自制的电路板回流焊接路已经成为我工作台上不可或缺的一个设备了。有了它以后,我在设计电路板的时候越来越大胆,选择元器件越来越小,完全不用担心做测试板的时候会不会遇到焊接难题了。

下面就是一组最近一批“烤”出来的电路样板:

在激光切割的网板上敷焊膏

在激光切割的网板上敷焊膏

移除网板,焊膏很漂亮地归整在焊盘上

移除网板,焊膏很漂亮地归整在焊盘上

放置各种元器件

放置各种元器件

准备开“烤”

准备开“烤”

烤好以后的成品!

烤好以后的成品!

Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(3)

如果你读过我bootloader开发系列文章的第二篇, 你应该了解了如何从MPLAB编译好的hex二进制文件中抽取所需要信息,包括数据的地址以及其内容。从另一个角度看,hex文件其实就是一个对照表,告 诉你某一个数据应该放在单片机闪存的哪个部位。这篇文章将着重讲一下dsPIC33E/PIC24E的用户闪存程序存储区(user memory space)的结构、一些解析hex文件的技巧以及GOTO/RESET指令的替换。

在能够将二进制数据放入单片机的闪存程序存储区相对应的位置之前,我们必须充分了解单片机闪存的结构。dsPIC33E/PIC24E单片机的用户 闪存存储区是一块连续的存储空间,地址范围从0×000000到0x02ABFE(对大容量型号单片机而言是0x0557FE)。为了便于批量编程/擦 除,整一块空间可以以不同单位来分割:擦除页(erase block,page)大小为1024个指令;编程块(program block,row)大小为128个指令。换句话说,一个擦除页含有8个编程块,或者1页有8行。所以从擦除页或者编程块的角度来看,地址上限是 0x02ABFE的单片机有86个擦除页或者684个编程块;而对0x0557FE大小的单片机来说则是171个擦除页或者1368个编程块。另外,小容 量单片机地址上限是0x02ABFE,换成十进制那就意味着175,104个地址。如果你将175,104除以1024或者128,你应该获得相应的 171和1368,你可能会纳闷为什么我前面说小容量的单片是86个擦除页和684个编程块,才一半?如果你不明白其中的道理,你可以查看我上篇文章,我 提到了每个指令需要两个地址空间。你也许还会注意到,175,104除以2再除以1024是应该得到85.5而不是86,你算的没错,Microchip 给最后一个擦除页只做了一半大小,512个指令,不过仍旧称它为“一个”页而已。擦除页是删除程序的最小单位,意味着你一旦执行一个删除命令,则至少要删 除一个页,即1024个指令,当然单片机还有一个整体擦除器件命令,顾名思义,一擦整个芯片都擦掉。同样的,编程块是编程的最小单位,跑一个块编程指令则 会一下子写入128个指令,当然这也意味着你在开始执行块编程之前,这个128个指令必须准备就位。锁存器(latch media)就是在写入闪存之前用来临时存放这些指令的地方,我会在后面的文章里详细解释这个。

如果你读了我上一篇文章,二进制代码提取应该是相当简单明了的。下面是一段我写的Python代码用来抽出相关数据以及按照地址重新编排。

def _Parse_Hex32(self):
    extended_Lineaer_Address = 0    # Left-shift by 16 bits
    extended_Segment_Address = 0    # Left-shift by 4 bits
    for i in range(0, len(self._hex32_Lines)):
        #print str(self._hex32_Lines[i])

        byte_Count, starting_Address, record_Type, data = self._Parse_Line(self._hex32_Lines[i])
        if record_Type == 1:        # End record
            if i != (len(self._hex32_Lines) - 1):
                raise Hex32_Invalid("Data type "End(0)" appears (line "" + str(i+1) + "") before the end of file.")
        elif record_Type == 2:      # Extended segment address
            if len(data) == 2:
                print ("!!! Warning !!!: Data type "Extended Segment Address(2)" appears on line"" + str(i+1) + "". ")
                extended_Segment_Address = (data[0] * 256 + data[1]) * 16
            else:
                raise Hex32_Invalid("Data type "Extended Segment Address(2)" (line"" + str(i+1) + "") contains more than two bytes.")
        elif record_Type == 4:      # Extended linear address
            if len(data) == 2:
                extended_Linear_Address = (data[0] * 256 + data[1]) * 256
            else:
                raise Hex32_Invalid("Data type "Extended Linear Address(4)" (line"" + str(i+1) + "") contains more than two bytes.")
        elif record_Type == 0:      # Data record
            for i in range(0, len(data) / 4):
                device_Address = (extended_Linear_Address + extended_Segment_Address + starting_Address) / 2 + i
                # Flag the LUT, divide the address by because the real device addres inrements by 2
                self._Flag_LUT(device_Address)

                # Fill in the hex into the array
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 0] = data[self._instruction_Size_In_Hex * i + 0]
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 1] = data[self._instruction_Size_In_Hex * i + 1]
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 2] = data[self._instruction_Size_In_Hex * i + 2]
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 3] = data[self._instruction_Size_In_Hex * i + 3]

        else:
            raise Hex32_Invalid("Unsupport data type: " + str(reccord_Type))

在本系列的第一篇文章里, 我提到了GOTO-RESET的指令必须用你指向bootloader开头地址的自定义数据替换。这样,一个被修改过GOTO-RESET指令的单片机在 上电开机以后就直奔bootloader,而不是单片机默认的用户程序起始地址。如果bootloader发现有新的固件程序达到,则将其烧入自己的闪存 并执行;如果没有,则执行闪存里已有的程序。

所以这里有两个步骤来处理GOTO-RESET问题:1、将编译器生成的GOTO-RESET数据提取出来并暂时保存好,然后用你自定义的 bootloader起始地址数据填入;2、拿出前面暂存好的原始GOTO-RESET数据放在bootloader的末尾,这样bootloader结 束之后,单片机将会开始执行用户程序。说白了,就是在正常的启动顺序中间插入一个bootloader过程,所以bootloader结束之后,还得将单 片机引回到正常执行用户程序的状态。

Microchip公司16位单片机dsPIC33E/PIC24E系列bootloader的开发(2)

上一篇“Microchip公司16位单片机dsPIC33/PIC24E系列bootloader的开发(1)”文章中提到了bootloader一些基本知识以及不同设计架构之间的比较,最终选择了把bootloader置于闪存空间尾部的方案。这篇文章我们要讨论一下bootloader的工作流程,让你了解一下整套bootloader所涉及的方方面面。

一般来说,设计完你的单片机程序以后,编译器会将你的C/C++或者汇编代码编译成二进制代码,通常保存在“.hex”结尾的文件里。这个hex文 件遵循Intel Hex32格式,我会在稍后细谈。然后你需要打开编程器的PC端软件,读入hex文件,确保编程器(Microchip的话是ICD2或者ICD3)已正 确连好,然后二进制代码就通过芯片上的PGC/PGD口写入单片机的闪存里。在这个流程里,Microchip已经帮我们做好了其中所有的工具链,分别 是:编译器(MPLAB IDE X)- 编程器PC端软件(MPLAB IPE X)- 编程器(ICD2或者ICD3) – 单片机接收端(PGC/PGD口)。在bootloader的流程了,你就得自己来开发其中的一些组件了,比如你可以继续使用MPLAB IDE X做为编译器,但是你得有自己开发的INTEL HEX32解析器,接下来就是你自己开发的硬件/软件负责将固件传到单片机上,有很多途径,可以是USB、UART、SPI、I2C甚至是SD卡,然后你 还要开发单片机上的接收/烧制代码,也就是真正的bootloader了。可见,所谓开发bootloader,其实是差不多整一个生态系统。

      1. 理解INTEL Hex32标准
        编译完单片机程序以后,编译器会生成一个hex后缀名的单片机固件。如果你用16进制文本编辑器打开这个文件,你会看到一行一行的16进制码。所有的这些16进制码都服从下面的格式:

         :BBAAAATTHHHH... ...HHHHCC
        

        一个字母表示一个16进制数字(即一个字节byte),其中:

        • [:] 每一行开始比有一个分号,没有列外
        • [BB] HHHH… …HHHH中字节的长度
        • [AAAA] 该行数据在芯片闪存中的起始地址
        • [TT] 该行数据类型,其中
          • 00:数据
          • 01:结尾标志
          • 02:Extended Segment Address Record,片地址偏移
          • 03:片地址偏移起始标志
          • 04:Extended Linear Address Record,块地址偏移
          • 05:块地址偏移起始标志
        • [HHHH… …HHHH] 数据部分
        • [CC] CRC循环冗余校验码

        详细的INTEL HEX32标准细节就不在这里解释了,网上很容易找到。Mirochip的DS70619B文档:dsPIC33E/PIC24E闪存编程规范就是一个很 好的参考。接下来我会详细讲述一下INTEL HEX32在dsPIC33/PIC24E上的具体实现,总结一下散落在多个文档中的一些零星要点。让我们从一个具体的例子开始来加深你的理解:

        :020000040108EA
        :0200000212FFBD
        :0401000090FFAA5502
        :00000001FF
        
          • 块地址偏移0108
          • 片地址偏移12FF
          • 原起始地址0100
          • 计算真实地址:0108左移16位变成0108 0000,12FF左移4位变成0001 2FF0,原起始地址0000 0100,加上偏移量后得到真实地址为:0109 30F0
          • 最终可以将这段hex解释为
            地址 数据
            010930F0 90
            010930F1 FF
            010930F2 AA
            010930F3 55
            

            值得注意的是,一旦含有“02”或者“04”数据类型的16进制代码行出现,则之后的所有行的数据地址都需要偏移,直至出现新一个“02”或者“04”,那么就需要重新计算新的偏移量。

  1. INTEL HEX32在dsPIC33E/PIC24E上的具体实现
    Microchip在INTEL HEX32的基础上增加了一些自己的规则。要想正确地解析dsPIC33E/PIC24E的hex十六进制文件,就必须深入Microchip为数众多的文档才能窥探到庐山真面目,下面就是一些汇总:

        1. dsPIC33E/PIC24E的每个指令(instruction)占用3个字节(byte)或者24位(bit),与此相对应的,用户闪存程 序存储区也是同样的3个字节宽度。这里问题就来了,标准INTEL HEX32格式中,每个数据总是4字节宽度。Microchip为了遵循此标准,编译生成的hex文件中每个指令数据也是4个字节,只不过最右边的一个字 节总是0×00,被称为“phantom byte(虚拟字节)”,顾名思义,就是没有实际作用纯粹用来充数字节。所以上面提到的BB部分除以4就是该行十六进制码中所含指令的数量。
        2. 用户闪存程序存储区的地址是以2为递增单位,这是为了照顾上面所述的3个字节宽度的指令。可以这样理解,闪存构造是以两个字节为一最小单位,3个 字节的指令将占去两个闪存地址空间,尽管有一个字节是“phantom byte”。有这个“phantom byte”也不意味着浪费了一个字节,因为这个字节在闪存里没有实现,只是为了照顾INTEL HEX32标准而凑数的。可以从Microchip的DS70609C_CN文档中得到验证“24位闪存程序存储器可被视为并排放置的两个16位空间,每 个空间都有相同的地址范围……由于闪存程序存储器只有24位宽,因此TBLRDH和TBLWTH指令能寻址闪存程序存储器中并不存在的最高字节。该字节被 称为“虚拟字节”。同时这也解释了dsPIC33EP512MU810的用户闪存存储区的地址上限为0x557EE(即十进制350,206)却只能存储 175,104(即350,206的一半)个指令了。
        3. INTEL HEX32文件中的“AAAA”部分表示的是该行数据的起始地址,但是需要除以2才是真正相对应芯片闪存的实际地址。
        4. 理解GOTO和RESET指令 Microchip编译好的Intel Hex32文件中,第二行一定是GOTO和RESET指令。该指令在芯片重置或者启动的时候被访问,告知应该从哪里开始读取用户程序。这样的其实地址为 “0”,只含有两个指令即8个字节(实际有用字节为6,2各位虚拟字节)。第一个指令总是以0×04结尾(不包括虚拟字节),表示所指向地址的低16位, 前两个字节就是改16位的地址。第二个指令总是以0×00结尾,表示所指向地址的高16位,同样的第一第二个字节就是该16位的地址。下面是一个例子:
          :0800000000a80400020000004a
          :08 0000 00 00a80400 02000000 4a
          (1)(2)  (3)(4)      (5)      (6)
          

          (1)这一行有8个字节数据
          (2)起始地位为0
          (3)类型为数据
          (4)第一个指令:00 a8 04 00(虚拟字节),可以解释为:指向低16位为0xa800的地址
          (5)第二个指令:02 00 00 00(虚拟字节),可以解释为:指向高16位为0×0002的地址
          (6)CRC循环冗余校正码
          这一行综合起来可以解释为,跳转到0x02a800地址。再来一个例子:

           :080000000002040000000000f2
          

          这一行的意思是,跳转到0×200地址。(这是一个dsPIC33FJ256GP710A芯片正常编译后的实际hex文件,是指在启动后跳向用户程 序的第一行。dsPIC33F的一个储存页为512个指令大小,0×200即跳过第一个含有GOTO/RESET以及中断向量表的储存页。不同于 dsPIC33F,我们讨论的dsPIC33E系列的储存页大小升级到了1024个指令,这个我会在后面一篇文章里提到。)