Jacktang

  • 2019-03-22
  • 发表了主题帖: pspice学仿真电路模拟的一下总结

    本帖最后由 Jacktang 于 2019-3-22 07:56 编辑 Pspice现在是集成到Cadence中的一个电路仿真工具,能通过Pspice模型的原理图仿真电路的输出结果。其大致步骤是: 1. 创建仿真工程 执行菜单命令File->New->Project,选择Analog or Mixed A/D,然后选择一个模板(这个看自己需要,也可以是空工程) 2. 添加仿真元件库 仿真所用的元件必须要有Pspice模型,Cadence安装目录下\tools\capture\library\pspice中所有的元件库都含有Pspice仿真模型。 3,电路特性分析类型和分析参数设置; 可在capture环境下新建或修改profile设置重新进行模拟。模拟类型分组设置结果存放在以SIM为扩展名的文件中。 4,运行pspice A/D程序,对电路进行模拟分析; 5,模拟结果的显示和分析; 完成电路模拟分析后,pspice按照电路特性分析的类型分别将计算结果存入扩展名为out的ASCII码输出文件以及扩展名为DAT的二进制文件中。 6,电路设计优化; 优化模块(optimizer)可以调用。 7,设计修正 电路设计出现问题或者分析参数出现问题,可能需要多次循环才可以满足要求。OUT文件中存放有错误信息。也可以灾波形显示分析窗口(probe)分析错误信息。 8,设计结果输出 得到符合要求的电路设计后,就可以调用capture输出全套电路图纸,包括各种统计报表。也可以根据需要将电路设计图数据传送给Orcad/layout,继续进行印刷电路板设计。

  • 发表了主题帖: 高手分享EMI整改经验

    关于晶体部份: 1、晶体到MCU的两条线不要太细,尽量短直,且这两条线与两个负载电容所包围的面积要越小越好,电容地端,最好单独用较宽的走线单独引至MCU振荡地,不要与大面积地铜箔相连; 2、晶体背面最好是整片的地铜箔,不要走其它线,也不要在晶体正面上方走别的线; 3、有的MCU与不适合的晶体配合,振幅过高,产生截顶失真,便会产生较强的基波及强烈的谐波辐射,这种情况需在Xout上造近MCU一端串几十至几百欧电阻,让振幅峰峰值降至VCC的1/2~2/3为宜; 高速线,一般是SDRAM、及数字视频信号的VCLK了,最好在其走线背面有地铜层,没有条件的,至少要有一条较宽的地线“护送”,能包地就更理想了,有的时候需要在靠MCU一端串电阻,消除过冲(过冲对辐射影响很大),不要串得太大,以免引起延迟;还有就是背面不要有平行的线,正面也是;另外就是USB,要走差分线; 低速较长的线,也不容忽视,尽可能用RC抑制高频分量,靠近对外的接口处,要串磁猪, 电源进线处,串小的共模电感 总而言之,辐射过强是较大的dv/dt,及较大的环路面积引起,想办法抑制电压瞬变,降低信号过冲,缩小信号与电流回流的环路面积。还有就是小天线效应 最后的没有办法的办法,才是屏蔽,成本十分昂贵

  • 发表了主题帖: TTL 和CMOS 区别的基础

    1,TTL电平: 输出高电平>2.4V,输出低电平<0.4V。在室温下,一般输出高电平是3.5V,输出低电平是0.2V。最小输入高电平和低电平:输入高电平>=2.0V,输入低电平<=0.8V,噪声容限是0.4V。 2,CMOS电平: 1逻辑电平电压接近于电源电压,0逻辑电平接近于0V。而且具有很宽的噪声容限。 3,电平转换电路: 因为TTL和COMS的高低电平的值不一样(ttl 5v<==>cmos 3.3v),所以互相连接时需要电平的转换:就是用两个电阻对电平分压,没有什么高深的东西。哈哈 4,OC门,即集电极开路门电路,OD门,即漏极开路门电路,必须外接上拉电阻和电源才能将开关电平作为高低电平用。否则它一般只作为开关大电压和大电流负载,所以又叫做驱动门电路。 5,TTL和COMS电路比较: 1)TTL电路是电流控制器件,而coms电路是电压控制器件。 2)TTL电路的速度快,传输延迟时间短(5-10ns),但是功耗大。COMS电路的速度慢,传输延迟时间长(25-50ns),但功耗低。COMS电路本身的功耗与输入信号的脉冲频率有关,频率越高,芯片集越热,这是正常现象。 3)COMS电路的锁定效应: COMS电路由于输入太大的电流,内部的电流急剧增大,除非切断电源,电流一直在增大。这种效应就是锁定效应。当产生锁定效应时,COMS的内部电流能达到40mA以上,很容易烧毁芯片。 防御措施: 1)在输入端和输出端加钳位电路,使输入和输出不超过规定电压。 2)芯片的电源输入端加去耦电路,防止VDD端出现瞬间的高压。 3)在VDD和外电源之间加限流电阻,即使有大的电流也不让它进去。 4)当系统由几个电源分别供电时,开关要按下列顺序:开启时,先开启COMS电路得电源,再开启输入信号和负载的电源;关闭时,先关闭输入信号和负载的电源,再关闭COMS电路的电源。 6,COMS电路的使用注意事项 1)COMS电路是电压控制器件,它的输入总抗很大,对干扰信号的捕捉能力很强。所以,不用的管脚不要悬空,要接上拉电阻或者下拉电阻,给它一个恒定的电平。 2)输入端接低内阻的信号源时,要在输入端和信号源之间要串联限流电阻,使输入的电流限制在1mA之内。 3)当接长信号传输线时,在COMS电路端接匹配电阻。 4)当输入端接大电容时,应该在输入端和电容间接保护电阻。电阻值为R=V0/1mA.V0是外界电容上的电压。 5)COMS的输入电流超过1mA,就有可能烧坏COMS。 7,TTL门电路中输入端负载特性(输入端带电阻特殊情况的处理): 1)悬空时相当于输入端接高电平。因为这时可以看作是输入端接一个无穷大的电阻。 2)在门电路输入端串联10K电阻后再输入低电平,输入端出呈现的是高电平而不是低电平。因为由TTL门电路的输入端负载特性可知,只有在输入端接的串联电阻小于910欧时,它输入来的低电平信号才能被门电路识别出来,串联电阻再大的话输入端就一直呈现高电平。这个一定要注意。COMS门电路就不用考虑这些了。 8,TTL电路有集电极开路OC门,MOS管也有和集电极对应的漏极开路的OD门,它的输出就叫做开漏输出。OC门在截止时有漏电流输出,那就是漏电流,为什么有漏电流呢?那是因为当三机管截止的时候,它的基极电流约等于0,但是并不是真正的为0,经过三极管的集电极的电流也就不是真正的0,而是约0。而这个就是漏电流。开漏输出:OC门的输出就是开漏输出;OD门的输出也是开漏输出。它可以吸收很大的电流,但是不能向外输出的电流。所以,为了能输入和输出电流,它使用的时候要跟电源和上拉电阻一起用。OD门一般作为输出缓冲/驱动器、电平转换器以及满足吸收大负载电流的需要。 9,什么叫做图腾柱,它与开漏电路有什么区别? TTL集成电路中,输出有接上拉三极管的输出叫做图腾柱输出,没有的叫做OC门。因为TTL就是一个三极管,图腾柱也就是两个三极管推挽相连。所以推挽就是图腾。一般图腾式输出,高电平400UA,低电平8MA

  • 发表了主题帖: flash锁死 仿真器无法连接芯片 报错No target connected

    原因:程序中把具有复用功能SW/JTAG的GPIO口占用了 解决思路:擦除芯片原有程序 解决方法: 1、(推荐)将RST接地,再连接SW/JTAG下载程序(新程序中不要再占用SWCLK和SWCLD),在下载中看见芯片擦除成功了就松开RST接地; 2、使用软件擦除芯片程序(什么软件我不记得了0.0),另外RST也要接地 3、最笨的方法 :使用串口(TX/RX,用软件mcuisp)下载一个新程序(HEX文件)...,同样新程序中不要再占用SWCLK和SWCLD,不然还会再次锁死; 注1:正常的下载器或者仿真器其实有五个引脚的除了VCC、GNG、SWCLK、SWDIO、还有一根接MUC上的RST ,下载或者仿真时会自己去复位一下,只是用的很少,市面上那些仿的JLINK也只引了四根线,像我这样的新手就 容易忽略 注2:在程序里边加入GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST,ENABLE); 来彻底解除限制,             或用写寄存器的方式,SWG_CFG[2.0]写入000

  • 发表了主题帖: TPS54331 DCDC 纹波 干扰 收音机 原因

    TPS54331器件是一款28V,3A非同步降压转换器,集成了低RDS(on)高端MOSFET。为了提高轻载时的效率,自动激活脉冲跳跃Eco模式功能。此外,1μA关断电源电流允许器件用于电池供电应用。具有内部斜率补偿的电流模式控制简化了外部补偿计算,减少了元件数量,同时允许使用陶瓷输出电容器。电阻分压器可编程输入欠压锁定的迟滞。过压瞬态保护电路可在启动和瞬态条件下限制电压过冲。逐周期电流限制方案,频率折返和热关断可在发生过载情况时保护器件和负载。 具有 Eco-mode 的 3.5V 至 28V 输入、3A、570kHz 降压转换器 用了一个TPS54331 把12 V 转5V后,再经过一个LDO转换为3.3V给收音IC,结果干扰非常大。 这之前用的是LM2596,对收音机干扰很小。分析输出纹波大,但是一直找不到原因。 最后,经过排查,对比54331的datasheet,发现54331对输入脚要求很高,必须使用一个无极性大电容和几个小电容贴近输入脚几个毫米内放置,干扰问题解决。排查问题耽误了2天。

  • 2019-03-17
  • 回复了主题帖: ARM单片机与ARM内核

    ARM授权从高到低大体分为三种,即架构授权、内核授权以及使用授权(实际的授权中,授权费用会根据其制造特性、用途、对架构的改动以及所选ARM架构版本的不同特性而出现较大差异)。   架构授权:         顾名思义,指令集授权是指企业购买了架构级的ARM处理器设计、制造许可。有了这一级别的授权,厂商便可以从整个架构和指令集方面入手,          对ARM架构进行大幅度改造,甚至可以对ARM指令集进行扩展或缩减,以便达到更高性能、更低功耗或更低成本等不同目的。          拥有架构授权的典型厂商包括高通、苹果、微软等财大气粗的行业巨头,因此架构级授权的价格可想而知;          当然,拥有架构级授权的高通和苹果所制造的ARM处理器也总是拥有更低的功耗和更高的性能。   内核授权:          内核授权则是指用户可以将其所购买的ARM核心应用到其自行设计的芯片中。但用户不得对其购买的ARM核心本身进行修改。           拥有内核级授权的厂商多如牛毛,包括德州仪器、博通、飞思卡尔、富士通以及Calxeda等等等等。           内核授权通常价格更低,但由于ARM架构本身拥有非常良好的扩展性,           因此,拥有内核授权的厂商亦可通过ARM架构的外围设计,在保持ARM处理器自身特性的前提下实现众多功能。   使用授权:          作为最低的授权等级,拥有使用授权的用户只能购买已经封装好的ARM处理器核心,而如果想要实现更多功能和特性,           则只能通过增加封装之外的DSP核心的形式来实现(当然,也可以通过对芯片的再封装方法来实现)。           由于担心对知识产权保护不力,ARM对很多中国背景的企业均采取这一级别的授权。   间接授权:          需要说明的是,上述三种级别的授权均不允许被授权者再次出售ARM架构授权。而对于半导体工厂而言,ARM通常会对其进行特殊授权。           这使得台积电、三星半导体等拥有半导体代工业务的企业不仅能够直接向用户出货拥有ARM内核的芯片,更在一定情况下拥有重置ARM内核的实力和权力。这也就让很多根本无法取得ARM授权的企业能够间接的使用或拥有ARM核心;当然,这种方法所带来的成本通常也比要直接从ARM手中买授权的价格高出数倍。简单的说,芯片代工厂在某种程度上扮演了ARM授权代理商的角色,           其存在可以满足某些用户小批量生产以及早期论证和试验的需要。

  • 发表了主题帖: 无线通信中的信道讲解

    信道是对无线通信中发送端和接收端之间的通路的一种形象比喻,对于无线电波而言,它从发送端传送到接收端,其间并没有一个有形的连接,它的传播路径也有可能不只一条(正如前面所说的电波的传播方式提到的),但是我们为了形象地描述发送端与接收端之间的工作,我们想象两者之间有一个看不见的道路衔接,把这条衔接通路称为信道。信道有一定的频率带宽,正如公路有一定的宽度一样。 正如前面所说的那样,无线信道中电波的传播不是单一路径来的,而是许多路径来的众多反射波的合成。由于电波通过各个路径的距离不同,因而各个路径来的反射波到达时间不同,也就是各信号的时延不同。当发送端发送一个极窄的脉冲信号时,移动台接收的信号由许多不同时延的脉冲组成,我们称为时延扩展。 同时由于各个路径来的反射波到达时间不同,相位也就不同。不同相位的多个信号在接收端迭加,有时迭加而加强(方向相同),有时迭加而减弱(方向相反)。这样,接收信号的幅度将急剧变化,即产生了快衰落。这种衰落是由多径引起的,所以称为多径衰落。 此外,接收信号除瞬时值出现快衰落之外,场强中值(平均值)也会出现缓慢变化。主要是由地区位置的改变以及气象条件变化造成的,以致电波的折射传播随时间变化而变化,多径传播到达固定接收点的信号的时延随之变化。这种由阴影效应和气象原因引起的信号变化,称为慢衰落。 而且,由于移动通信中移动台的移动性,如前所说那样,无线信道中还会有多普勒效应。在移动通信中,当移动台移向基站时,频率变高,远离基站时,频率变低。我们在移动通信中要充分考虑"多普勒效应"。虽然,由于日常生活中,我们移动速度的局限,不可能会带来十分大的频率偏移,但是这不可否认地会给移动通信带来影响,为了避免这种影响造成我们通信中的问题,我们不得不在技术上加以各种考虑。也加大了移动通信的复杂性。 综上所述,无线信道包括了电波的多径传播,时延扩展,衰落特性以及多普勒效应,在移动通信中,我们要充分考虑这些特性以及解决的方案。

  • 发表了主题帖: 几个概念让你初步了解智能车比赛

         处理器和单片机区别:处理器是一个内核,单片机是将处理器外接IO接口芯片,定时器模块,时钟模块,等多个不同模块组成的一个嵌入式小系统,51单片机是8位单片机,k60是32位,8位32位是处理器数据总线宽度,同一时间处理的数据个数,位数越多,处理器相同时间内处理数据越多.      Arm处理器:arm公司将自己设计的芯片分给多个厂家,然后厂家给它外接各种接口芯片,飞思卡尔公司就是其中之一,它设计了多款以arm处理器为核心的单片机,K60就是以arm_M4处理器做的,Kl46是arm_M0处理器.       时钟是干什么的:时钟相当于单片机的心脏,单片机内部运作就是在时钟为标准进行运作,处理器运行一条指令时间固定为几个时钟周期,时钟频率越高,运行一条指令的时间越短,所以时钟频率越高,单片机数据处理越快。但不是时钟越快越好,单片机有时钟运行的额定范围,太高了单片机会承受不了,51单片机时钟频率就是外接晶振11.0592MHZ或者12MHZ,咱们k60外部晶振是50MHZ.      锁相环倍频:锁相环增频是一个反馈回路最终保持鉴相器输入信号Vi和  压控振荡器输出信号VO率完全一致,Vo输出经过分频器n 分频后间鉴相器两边产生信号差,压控振荡器增强n倍使分频后Vo与输入Vi保持一致,达到倍频n倍效果. GPIO和PORT:GPIO是通用输入输出,PORT是引脚,端口,他们虽然指的同一个引脚但本质意义不同,PORT引脚具备多种功能,GPIO是它的功能之一,所以K60里面有个PORT寄存器和一个GPIO寄存器,设置成基本输入输出时候需要先设置PORT相关寄存器为GPIO功能,输入输出和读引脚状态就要去GPIO寄存器里面设置和读取.

  • 发表了主题帖: RT Thread IPC总结

    1、关中断的方法可以实现互斥,但是这时候是无法响应中断的 2、调度器上锁可以实现多任务的互斥,但是无法实现与中断的互斥 3、信号量,轻量级的互斥机制,因为初始值不一定为1,所以他没有所有者(拥有者)的概念,且没有解决优先级翻转的问题 4、互斥量是管理临界资源的一种有效手段,它使用优先级继承方法解决了优先级翻转的问题 5、事件主要特点是可以实现一对多,多对多的同步。事件集的关联形式可以是“逻辑或”和“逻辑与” 。 6、前面是多任务间的同步与互斥,邮件是线程,中断服务,定时器向线程发送消息的有效手段。邮箱与 线程对象等之间是相互独立的。线程,中断服务和定时器都可以向邮箱发送消息,但是只有线程能够接收 消息(因为当邮箱为空时,线程将有可能被挂起)。每封邮件的大小一般是4字节,一般是缓冲区指针。 7、消息队列是以队列的方式来管理消息,每个消息有相同的消息长度,发送的消息不能超过这个长度, 但是如果发送的消息小于这个长度,并没有表示真实长度的数值?      IPC 中一般detach是静态内存的管理模式,是删除内核对象,唤醒相应被阻塞的线程,delete是动态内存 的管理模式,是删除内核对象,释放申请的缓冲区,唤醒相应被阻塞的线程。

  • 发表了主题帖: ARM(Cortex-M3)的中断向量

    记得在DSP TMS32F2812中,中断向量的初始化是由一段地址拷贝代码完成的,在STM32(Cortex-M3)中没有显示的代码拷贝,只有启动代码进行了向量的初始化,一直以为是编译器在程序影像中自己完成了相关向量的拷贝,即,拷贝到固定的NVIC区,事实上并不是这样,cortex-m3并没有一块专门用于存放NVIC向量表的地方,这张表实际是存放在代码(程序映像)的开始,下面引用cortex-M3权威指南进行解释:     当发生了异常并且要响应它时,CM3需要定位其服务例程的入口地址。这些入口地址存储在所谓的“(异常)向量表”中。缺省情况下,CM3认为该表位于零地址处,且各向量占用4字节。因此每个表项占用4字节,如表7.6所示。          因为地址0处应该存储引导代码,所以它通常映射到Flash或者是ROM器件,并且它们的值不得在运行时改变。然而,为了支持动态重分发中断,CM3允许向量表重定位——从其它地址处开始定位各异常向量。这些地址对应的区域可以是代码区,但更多是在RAM区。在RAM区就可以修改向量的入口地址了。为了实现这个功能,NVIC中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08处),通过修改它的值就能重定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上“圆整”到2的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32个中断,则共有32+16(系统异常)=48个向量,向上圆整到2的整次幂后值为64,因此向量表重定位的地址必须能被64*4=256整除,从而合法的起始地址可以是:0x0, 0x100, 0x200等。向量表偏移量寄存器的定义如表7.7所示。 如果需要动态地更改向量表,则对于任何器件来说,向量表的起始处都必须包含以下向量:  主堆栈指针(MSP)的初始值  复位向量  NMI  硬fault服务例程 后两者也是必需的,因为有可能在引导过程中发生这两种异常。 可以在SRAM中开出一块空间用于存储向量表。在引导期间先填写好各向量,然后在引导完成后,就可以启用内存中的新向量表,从而实现向量可动态调整的能力。

  • 发表了主题帖: arm裸机程序对比单片机-个人一点感触

    以前自学arm的时候就接触过裸机的一些内容,但是有很多东西一直没有弄明白,现在自己报了培训班来系统的学习arm的内容。 已经学了有2个月了,最近刚好在学裸机开发的内容,和以前单片机开发大体上是差不多,都是在玩寄存器,但是有很多地方还是值得去注意的。下面分别说下自己的体会。 1-开发平台 相信用单片机的肯定也一定是用的集成开发环境IDE,比如有什么keil、IAR等。arm裸机开发当然也会是在一些集成环境中去做,不过由于arm到现在基本上很少有芯片是为了跑裸机来设计的,基本上都是要设计到操作系统。所以像keil什么的对很多arm的芯片是不支持的。这时候就得要自己想办法搭这样的一个开发环境,目前培训机构所学的是用eclipse软件来做。至于为什么要用这个软件,个人觉得是因为它可以实现自己选择编译器,调试器等东西(目前我也只接触了这两个)。 这里提出来两个概念编译器和调试器。一般一个集成开发环境都会有编译器、调试器、文本编辑器这三个东西。 编译器就是我们make project的一个软件,调试器就是在debug的时候用的软件,文本编辑器自然就是我们写入代码的地方。编译器有很多种GCC,ICC(avr单片机的一种)等,不同的IDE可能会集成不同的编译器。gcc对于不同的硬件平台也有不同的版本,有x86的,有arm的,有avr的等等,要做arm裸机开发就必须使用arm对应的gcc。 调试器目前我知道的主要是gdb,大家也基本都用gdb的包括一些IDE的大多用的也是GDB。目前我这边使用的是arm-none-eabi-gdb。 文本编辑器就不用说了。 2-编程 用单片机有很多东西都不需要自己处理,当然高手在处理复杂的单片机程序也会自己去处理的,比如makefile,这个是个很重要的东西,就是告诉编译器如何编译我们写好的代码,当我们代码比较多的时候就必须使用makefile来管理。 当然makefile只是一方面,在编程上比较重要的区别是程序从哪里开始入口的,这个是我以前做单片机开发的时候一直没有弄明白的。只知道c编程的时候从main函数开始写,就完事了。但实际上是怎么跑到main中的一直也不明白,虽然以前也学了汇编,但是在开发单片机的时候从来都没有写过汇编的东西,所以就不了解汇编的必不可少性。 arm程序的开始执行地方,也就是pc刚开始的值,是在编译的时候在makefile中由—Ttext指定地址,当然这个地址也可以在map.lds文件中指定。这个地址的处理通常会用汇编处理,在汇编中写好中断向量表和处理、一些关键设备的初始化,然后用一个b命令跳转到c代码中的main函数中去执行。 3-寄存器 由于我接触arm的这个编程也不是很深,目前感觉在写寄存器上和以前不一样的就是:以前做单片机寄存器和地址的对应一般在集成开发环境中就已经写好了,但是在现在需要使用什么寄存器都需要自己去写宏定义定义地址。这个事个人感觉还只是目前目前我的了解还比较少,像这些寄存器定义地址什么的去了公司肯定会直接用前人的,要不然每次开发一个项目都要自己去写定义地址表,太浪费时间,效率太低。

  • 2019-03-16
  • 发表了主题帖: H.264编码器的DSP移植与优化

    本帖最后由 Jacktang 于 2019-3-16 21:53 编辑      H.264编码器的实现版本主要有:JM、T264、X264。其中JM是H.264官方源码,实现H.264所有特征,但其程序结构冗长,只考虑引入各种新特性以提高编码性能,忽略编码复杂度,其复杂度极高,不宜实用;T264编码器编码输出标准的264码流,解码器只能解T264编码器生成的码流;X264是编码器注重实用,在不明显降低编码性能的前提下,努力降低编码的计算复杂度。这里,用X264编码器对DSP平台移植、优化。X264程序在DSP平台上实现及优化主要有:程序简化、代码移植、代码优化。 程序简化      X264编码器除支持H.264的基本档次外,还包含主要档次的某些功能选项以及其他功能模块,代码尺寸较大,因此需要将不必要的功能模块删除,以减小代码尺寸。主要做以下删减:删除X264程序中的解码部分,以及基本档次功能之外的CABAC、Bslice部分;X264程序是基于X86的PC平台,包含了SSE、MMX等。PC平台使用的优化技术,在DSP平台下无效:针对DSP平台特点,调整删减后的代码文件结构。 代码移植       TI公司的DSP开发工具CCS具有自己的ANSI C编译器和优化器,并有自己的语法规则和定义,经过上一步简化后得到纯C版本的X264编码器需要经过修改才能够在CCS下应用于具体的DSP。主要包括:①Visual c++、CCS对于变量和结构体的“重复定义”问题的不同处理,需更改头文件中变量和结构体定义的位置;②用功能相同的库函数代替CCS中没有的库函数,如strncasecmp;③数据格式的不同,用long代替CCS中没有的_int64格式;④按照CCS下C语言的规则定义数组;⑤修改系统配置参数的读取方式;⑥编写针对TMS320DM6446存储结构的CMD文件。如此,X264便可以在CCS下编译通过并运行。 代码优化      纯C版本的X264程序并没有利用DM6446的资源和并行机制,代码运行速度极低。因此必须对代码进行优化,提高处理性能。X264代码优化有以下3个层次:项目级优化、算法级优化和指令级优化: (1)项目级优化项目级优化主要是对CCS提供的各种编译参数进行选择、搭配、调整,如本文使用的选项-o3、-pm等;利用CCS编译器提供的优化功能,改善循环及多重循环体性能,进行软件流水,提高软件的并行性;改写不适合编译器优化的语句,使CCS能够对程序进行更好的优化。 (2)算法级优化进行算法级优化时。应使VC环境下的纯C版本与CCS下的版本同步更新,VC版本运行正确,既可以保证算法理论上的正确,又可以加快工作速度并减少问题的产生。该算法优化工作主要有以下几点:①运动估算法的选择:X264编码器提供3种可选的整像素运动估算法:X264_ME_ESA(全搜索法)、X264_ME_HEX(六边形搜索法)、X264_ME_DIA(小菱形搜索法)。在VC环境下使用纯C版本代码对同一视频序列使用3种不同的搜索方法进行编码。对比3种搜索方法在编码速度、峰值信噪比(PSNR)、码率方面的性能。对比之下X264_ME_ESA算法的峰值信噪比最高,X264_ME_HEX次之,X264_ME_DIA最低,但相互之间的质量差别并不大,码率差别也很小,但编码速度却有明显差距,X264_ME_DIA较前两者在编码速度上有明显的优势。经比较,选择使用X264_ME_DIA运动估计算法。②帧内预测模式的改进:在X264的帧内预测流程中加入提前终止模式选择的条件,改进算法的流程。进行16×16宏块帧内模式搜索时,在当前模式的开销小于已搜索过的模式的最小开销的一半时,终止16×16帧内预测模式选择,以当前模式为最佳16×16帧内预测模式。对4×4块也加入相同的条件,并且若当前4×4块帧内预测模式的预测开销比相应的最佳16×16块帧内预测模式的开销的1/16还要小,则终止4×4块的帧内预测模式选择,以当前预测模式作为最佳4×4块的帧内预测模式。改进后的帧内预测主体流程如图3所示,灰色部分为加入的判定条件。        帧间预测模式的改进:将当前的16×16宏块划分为4个8×8宏块,分别预测其运动矢量,然后以左右相邻、上下相邻的2个8×8块的运动矢量的差值和阈值相比较为依据,判定是否进行16×8、8×16等分块模式的预测,最后选择开销最小的划分模式为最佳帧间划分模式。 (3)指令级优化 DM6446一个时钟周期内可并行运行8条指令,一次可存取64位数据,内部拥有64个32位通用寄存器,并且支持对寄存器中的4个8位字节或2个16位字节分别进行运算处理,这些使得DM6446具有很强的并行运算能力。视频图像的像素尺寸一般是4的倍数,X264中像素的值是用8位或16位数据按矩阵形式有规律的存储,这种数据存储结构与DM6446的并行处理方式很契合。因此对X264程序进行指令优化充分发挥DM6446的并行运算能力,是提高编码器速度的关键。主要分为以下两部分:①使用内联函数优化;C6000编译器提供了许多内联函数intrinsics,它们是汇编指令映射的在线函数,不宜用C语言实现其功能的汇编指令都有对应的intrinsics函数。这样就可在C语言结构中直接使用内联函数实现对多个数据的并行运算操作。如:未使用内联函数优化前X264程序调用一次双线性内插函数只能计算一个亚像素点的值,而使用内联函数_mem4、_avgu4等进行优化后,一次可以计算4个亚像素点的值,大大提高了运算速度。②使用线性汇编语言优化:由于线性汇编不需要考虑寄存器分配、指令延迟、并行指令安排等因素。因此可以利用CCS提供的profile分析工具将使用频率高、耗时多的函数抽取出来,根据事先已知的数据间的相关性等信息,在程序中直接改写函数汇编,人工优化。涉及的算法有:SAD、SSD的计算;DCT变换;反DCT变换、亚像素搜索等。

  • 发表了主题帖: 图像算法移植到DSP及其优化步骤

    当你需要把已经写好的算法,移植到你的DSP开发板上并很好的跑起来,需要做哪些工作呢? 下面我分两部分来讲,第一分部是移植,第二部分为算法优化 移植: 1)如果你的算法是基本opencv这样的基本上开发的,你需要脱离opencv的环境。 2)如果你的算法是C++语言,请你改成标准的C语言。虽然DSP的开发环境是支持C++的,但是不建议你这么做。 3)修改你算法的内存分配,尽量内存一次分配好,DSP在算法不断的申请和释放时会有隐患。优先使用静态数组,会减轻很多工作量。 4)在CCS下建立工程,来调试你的算法,内存分配函数需要使用TI提供的函数。如果你的算法能够长期稳定的运行,那么恭喜你,你的算法移植就完成了。 优化: 算法优化,需要你能懂算法,也懂DSP。如果你只会写DSP程序,而不会算法,这对整个产品来说,是不能达到最优的。有些公司怕算法泄密,给优化人员一段或几段程序让其优化。我觉得这样做是很不合理的。除非你自己能控制大局,精通优化,这样才可行。 1)你需要对算法原理做一个深刻苦的理解,阅读相关的文章。 2)对你拿到的算法做全方位的熟悉。 3)做好上面的准备工作后,你要对算法的结构做重新的整理。依据DSP的特点,比如内存的分布。算法结构调整完成后,你的算法在DSP上速度应该有一个明显的提高了。 4)结构调整完成后,找到算法中比较费时的部分。确定我们需要优化的重点,这部分内容多是每张图像都要处理一次或多次的部分。对于算法启动时初始化部分的内容,一般不需要优化。 5)确定优化内容后,你首先考虑从语言结构上去做优化,这个时候应该还是C语言的。我不建议大家用TI提供的在C语言中使用优化嵌入的C库函数。 6)你把需要优化的函数改写为线性汇编或汇编函数。不断的调整软件流水,提高速率。 这个整个移植优化的工作就基本上做完了,其实实际工作中,移植优化的工作量往往会比较大,要不断的反复,找更好的方法。移植优化跟算法开发一样,是个细致的活。

  • 发表了主题帖: 嵌入式C编程经验 之 全局变量猛于虎

    本帖最后由 Jacktang 于 2019-3-16 21:43 编辑 /********************************************************************************* * Filename: 一线研发之声:嵌入式C编程经验 之 全局变量猛于虎 * key: 嵌入式  os-less  全局变量  单片机         嵌入式特别是单片机os-less的程序,最易范的错误是全局变量满天飞。这个现象在早期汇编转型过来的程序员以及初学者中常见,这帮家伙几乎把全局变量当作函数形参来用。在.h文档里面定义许多杂乱的结构体,extern一堆令人头皮发麻的全局变量,然后再这个模块里边赋值123,那个模块里边判断123分支决定做什么。每当看到这种程序,我总要戚眉变脸而后拍桌怒喝。没错,就是怒喝。我不否认全局变量的重要性,但我认为要十分谨慎地使用它,滥用全局变量会引申带来其它更为严重的结构性系统问题。 诸位看官,且听我细细道来。 1. 它会造成不必要的常量频繁使用,特别当这个常量没有用宏定义“正名”时,代码阅读起来将万分吃力。 2. 它会导致软件分层的不合理,全局变量相当于一条快捷通道,它容易使程序员模糊了“设备层”和“应用层”之间的边界。写出来的底层程序容易自作多情地关注起上层的应用。这在软件系统的构建初期的确效率很高,功能调试进度一日千里,但到了后期往往bug一堆,处处“补丁”,雷区遍布。说是度日如年举步维艰也不为过。 3. 由于软件的分层不合理,到了后期维护,哪怕仅是增加修改删除小功能,往往要从上到下掘地三尺地修改,涉及大多数模块,而原有的代码注释却忘了更新修改,这个时候,交给后来维护者的系统会越来越像一个“泥潭”,注释的唯一作用只是使泥潭上方再加一些miyan瘴气。 4. 全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。这个时候如果处理不当,系统的bug就是随机出现的,无规律的,这时候初步显示出病入膏肓的特征来了,没有大牛来力挽狂澜,注定慢性死亡。         无需多言,您已经成功得到一个畸形的系统,它处于一个神秘的稳定状态!你看着这台机器,机器也看着你,相对无言,心中发毛。你不确定它什么时候会崩溃,也不晓得下一次投诉什么时候道理。 然后,我告诉大家现实层面的后果是什么。 1.“老人”气昂昂,因为系统离不开他,所有“雷区”只有他了然于心。当出现紧急的bug时,只有他能够搞定。你不但不能辞退他,还要给他加薪。 2. 新人见光死,但凡招聘来维护这个系统的,除了改出更多的bug外,基本上一个月内就走人,到了外面还宣扬这个公司的软件质量有够差够烂。 3.随着产品的后续升级,几个月没有接触这个系统的原创者会发现,很多雷区他本人也忘记了,于是每次的产品升级维护周期越来越长,因为修改一个功能会冒出很多bug,而按下一个bug,会弹出其他更多的bug。在这期间,又会产生更多的全局变量。终于有一天他告诉老板,不行啦不行啦,资源不够了,ram或者flash空间太小了,升级升级。 4. 客户投诉不断,售后也快崩溃了,业务员也不敢推荐此产品了,市场份额越来越小,公司形象越来越糟糕。        要问我的对策吗,只有两个原则: 1. 能不用全局变量尽量不用,我想除了系统状态和控制参数、通信处理和一些需要效率的模块,其他的基本可以靠合理的软件分层和编程技巧来解决。 2. 如果不可避免需要用到,那能藏多深就藏多深。 1)如果只有某.c文件用,就static到该文件中,顺便把结构体定义也收进来; 2)如果只有一个函数用,那就static到函数里面去; 3)如果非要开放出去让人读取,那就用函数return出去,这样就是只读属性了; 4)如果非要遭人蹂躏赋值,好吧,我开放函数接口让你传参赋值;5)实在非要extern强奸我,我还可以严格控制包含我.h档的对象,而不是放到公共的includes.h中被人围观,丢人现眼。          如此,你可明白我对全局变量的感悟有多深刻。悲催的我,已经把当年那些“老人”交给我维护的那些案子加班全部重新翻写了。你能明白吗,不要让人背后唾弃你哦。          承蒙小编抬爱,推荐了本博文,感激之余心中惶恐,特地详细看了下回复。这个主题最早是在论坛发表的,我发现那里的回复还是比较热烈的,也很高兴能够听到不同的声音。对于一些网友提到的,如果大量使用局部变量也会容易造成栈溢出的问题,还提到程序模型的概念。言之有理。所以特地来补充一下意见: 1.全局变量是不可避免要用到的,每一个设备底层几乎都需要它来记录当前状态,控制时序,起承转合。但是尽量不要用来传递参数,这个很忌讳的。 2.尽量把变量的作用范围控制在使用它的模块里面,如果其他模块要访问,就开个读或写函数接口出来,严格控制访问范围。这一点,C++的private属性就是这么干的。这对将来程序的调试也很有好处。C语言之所以有++版本,很大原因就是为了控制它的灵活性,要说面向对象的思想,C语言早已有之,亦可实现。 3.当一个模块里面的全局变量超过3个(含)时,就用结构体包起来吧。要归0便一起归0,省得丢三落四的。 4.在函数里面开个静态的全局变量,全局数组,是不占用栈空间的。只是有些编译器对于大块的全局数组,会放到和一般变量不同的地址区。若是在keil C51,因为是静态编译,栈爆掉了会报警,所以大可以尽情驰骋,注意交通规则就是了。 5.单片机的os-less系统中,只有栈没有堆的用法,那些默认对堆分配空间的“startup.s”,可以大胆的把堆空间干掉。 6.程序模型?如何分析抽象出来呢,从哪个角度进行模型构建呢?很愿意聆听网友的意见。本人一直以来都是从两个角度分析系统,事件--状态机迁移图 和 数据流图,前者分析控制流向,完善UI,后者可知晓系统数据的缘起缘灭。这些理论,院校的《软件工程》教材都有,大家不妨借鉴下。只不过那些理论,终究是起源于大型系统软件管理的,牛刀杀鸡,还是要裁剪一下的。 1、        消息机制弄好了可以避免全局变量 代价是效率 c++的class封装变量本质上还是全局变量 多个模块访问的话你得用class内的static 用c的话 折衷的办法是封在struct里面 然后用指针传struct变量的地址 这样没有全局变量 也不会有消息机制的效率问题 代价是模块耦合度 我比较喜欢这种办法 2、        在无操作系统的情况下,全局变量不是什么大问题,只要你的全局变量在中断函数里面没有被用到,那么就不会影响到你的全局变量的值,如果一个全局变量要在中断里面改变的话那就设置标志呗!!! 3、 一个子程序与外界的直接联系越少,则内聚性越好,而与外界联系,就是全局变量和参数 ,所以参数和全局变量越少越好,否则程序很难维护,可读性也不高。而且,全局变量占用固定地址不变,在函数中,或局部要读取它时还得到外界去找,而且在一 个大点的程序中如果大量使用全局变量会降低程序的可读性,而且占用了大量的有限空间,降低运行速度。 全局变量有些时候是无法避免的,只能尽量减少全局变量。我能想到的主要有三个方面: 1、通过函数参数传递数据或指针。对于回调函数,通常有一个参数是自定义的数据指针,可以利用这个参数传递类指针或者自定义一个结构的指针。 2、只在某一个函数中使用的变量可以定义为局部静态变量(在函数内部定义,前面加static修饰)。 3、把全局变量定义成一个结构,定义一个全局的结构体变量。 4、全局变量有其存在的必然法则,所谓全局变量不安全的论调也是杞人忧天,所有的不安全隐患来源于代码编写的不可靠,是人为因素和规范漏洞。有公司要求减 少全局变量这是对的,不许使用全局变量那是非常的不靠谱,系统编程中不止一处需要使用全局变量。在有些操作系统中可以使用系统规定的任务标志代替全局变 量,但是这只是全局变量的变形形式,并不是没有使用全局变量。而在某些没有操作系统的系统中就必须也只能使用全局变量了。使用全局变量最基本的一条就是能 使用静态全局变量不使用动态全局变量。 2、如果你真的想替代全局变量,其实是有办法的,就是使用单独的内存驱动。这种方法其实就是打肿脸充胖子,也许你觉得这样可靠,但其可靠性并不见得真的就高,而且非常占用资源。 其使用方式很简单,就是要实现4个函数 (1)申请标志 (2)读取标志 (3)更改标志 (4)删除标志 其中申请标志要保证只能使用一次 3、C++封装的概念在于面向对象,但是你要清楚一点C++只是C得扩充,是C的高级实现,所以所有在C++中能实现的功能都可以在C中实现,只不过实现比较复杂一些,如果感兴趣可以去看些C面向对象的书,其教你怎么用C实现面向对象。

  • 发表了主题帖: 嵌入式C编程中的全局变量重复声明问题

         用C语言编写程序的时候,我们经常会遇到这样一种情况:希望在头文件中定义一个全局变量,然后包含到两个不同的c文件中,希望这个全局变量能在两个文件中共用。比如在子函数led.c的头文件led.h中声明了一个全局变量int i,而led.h即包含在main.c中,又包含在led.c中,所有有些人就天真的认为,这样就可以正常使用,但你编译的时候,就会出现如下报错: ..\OBJ\test.axf: Error: L6200E: Symbol TimingDelay multiply defined (by systick.o and main.o). 也就是说编译器认为我们重复定义了i这个变量。这是因为#include命令就是原封不同的把头文件中的内容搬到#include的位置,所以相当于main.c和led.c中都执行了一次int i,而C语言中全局变量是项目内(或者叫工程内)可见的,这样就造成了一个项目中两个变量i,编译器就认为是重复定义。 正确解决的办法如下: (1)main.c #include"led.h" int i;          //定义一个全局变量 (2)led.c #include"led.h" extern int i;//申明这个变量为外部变量,是在其他的c文件中定义的全局变量。 注意声明和定义不一样,定义会分配变量空间,而声明的话就不再分配空间,只是告诉编译器,这个变量在别的地方,我只是使用它。

  • 发表了主题帖: ARM单片机与ARM内核

        人们常常把ARM架构和ARM单片机混淆,其实使用ARM内核的单片机有很多,比如ST公司的STM32F103,它就是使用ARM-Cortext M3内核,那么究竟什么是ARM内核呢?      MCU(Microcontrol Unit)俗称单片机,它有CPU,ROM,RAM,I/O,中断系统,定时器,时钟系统,ADC,DAC等,其中的CPU是核心,它有ARM公司设计,而别的外设单元,比如定时器,IO口,就灵活的由单片机厂商设计制造。而事实上,ARM公司早已把寻址空间映射好了,比如一部分预留给片上外设,一部分预留给RAM,厂家只要参考这个,就可以自定义添加自己设计的外设。这就是基于ARM内核的MCU。 关于总线:       ARM Cortex-M3的总线是AHB,它通过桥接器与APB2和APB1连接,其中APB1可以访问DAC,I2C,UZRT等,而APB2可以访问ADC,GPIO等。有意思的是,GPIO口就像RAM一样,也是通过总线访问的。

  • 2019-03-15
  • 发表了主题帖: PIC单片机内部EEPROM的操作

    //-------------------------------------------------------- //EEPROM字节写程序 //功能: 写一个字节到内部EEPROM //入口: EEADR =地址 // EEDATA =数据 //-------------------------------------------------------- void write_eeprom ( void ) { // while ( WR )     //等待上一次写操作结束 // { //  asm ("clrwdt");    //喂狗 // } EEPGD = 0 ;      //设置访问目标为EEPROM WREN = 1 ;     //允许进行写操作 GIE = 0 ;        //禁止中断 EECON2 = 0x55 ; EECON2 = 0xAA ; WR = 1 ;         //启动一次写操作 GIE = 1 ;        //使能中断 WREN = 0 ;     //关闭写操作 } //-------------------------------------------------------- //EEPROM字节读程序 //功能: 从内部EEPROM读一个字节 //入口: EEADR =地址 //出口: EEDATA =数据 //-------------------------------------------------------- void read_eeprom( void ) { EEPGD = 0 ;      //设置访问目标为EEPROM RD = 1 ;        //启动一次读操作 } //-------------------------------------------------------- //FLASH字节写程序 //功能: 写一个字节到内部FLASH //入口: EEADRH,EEADR =地址 // EEDATH,EEDATA =数据 //-------------------------------------------------------- void write_flash ( void ) { EEPGD = 1 ;      //设置访问目标为FLASH WREN = 1 ;     //允许进行写操作 GIE = 0 ;        //禁止中断 EECON2 = 0x55 ; EECON2 = 0xAA ; WR = 1 ;         //启动一次写操作 asm ("nop") ; asm ("nop") ; GIE = 1 ;        //使能中断 WREN = 0 ;     //关闭写操作 } //-------------------------------------------------------- //FLASH字节读程序 //功能: 从内部FLASH读一个字节 //入口: EEADRH,EEADR =地址 //出口: EEDATH,EEDATA =数据 //-------------------------------------------------------- void read_flash( void ) { EEPGD = 1 ;      //设置访问目标为FLASH RD = 1 ;        //启动一次读操作 asm ("nop") ; asm ("nop") ; } 2.方法二: void WriteEE(unsigned char addr,unsigned char data)        //写EEPROM    {     do{;}     while(WR==1);                        //上一次写操作是否完成     EEADR=addr;                            //EEPROM地址     EEDATA=data;                        //准备写入EEPROM的数据     EEPGD=0;                            //指向EEPROM数据储存器     WREN=1;                                //使能写操作     EECON2=0x55;                        //设置通用参数     EECON2=0xAA;                        //设置通用参数     WR=1;                                //开始写     do{;}     while(WR==1);                        //等待写操作完成     WREN=0;                                //禁止写操作    } // unsigned char ReadEE(unsigned char addr)                //读EEPROM    {     unsigned char num;     do{;}     while(RD==1);                        //上一次读操作是否完成     EEADR=addr;                            //EEPROM地址为00H     EEPGD=0;                            //指向EEPROM数据储存器     RD=1;                                //开始读     do{;}     while(RD==1);                        //等待读操作完成     num=EEDATA;                            //读出     return(num);                        //返回读出的数    } 说明:两个程序基本步骤是一致的。个中的差别是: 1、一程序中更严密,其中增加了对WR和RD标志位的判别,缺点是实时性较差。 2、而二程序中没有这个对WR和RD标志位的判别。那是因二将该判别的动作放在了上级程序中。也就是说,二在调用write_eeprom 函数之前,会先行判断WR。确信上次写操作已经结束后,才去调用新一次的写操作。这样做的目的是为了系统的实时性。 3.方法三: PICC系统自带“ PIC.H”文件中,已经内嵌了这两个函数。! 以下是“ PIC.H”文件中的内容: /***********************************************************************/ /****** EEPROM memory read/write macros and function definitions *******/ /***********************************************************************/ /* NOTE WELL:    The macro EEPROM_READ() is NOT safe to use immediately after any    write to EEPROM, as it does NOT wait for WR to clear.  This is by    design, to allow minimal code size if a sequence of reads is    desired.  To guarantee uncorrupted writes, use the function    eeprom_read() or insert while(WR)continue;    before calling EEPROM_READ(). */ #if EEPROM_SIZE > 0 #ifdef __FLASHTYPE // macro versions of EEPROM write and read #define EEPROM_WRITE(addr, value) \ do{ \ while(WR)continue;EEADR=(addr);EEDATA=(value); \ EECON1&=0x7F;CARRY=0;if(GIE)CARRY=1;GIE=0; \ WREN=1;EECON2=0x55;EECON2=0xAA;WR=1;WREN=0; \ if(CARRY)GIE=1; \ }while(0) #define EEPROM_READ(addr) ((EEADR=(addr)),(EECON1&=0x7F),(RD=1),EEDATA) #else // else doesn't write flash #define EEPROM_WRITE(addr, value) \ do{ \ while(WR)continue;EEADR=(addr);EEDATA=(value); \ CARRY=0;if(GIE)CARRY=1;GIE=0; \ WREN=1;EECON2=0x55;EECON2=0xAA;WR=1;WREN=0; \ if(CARRY)GIE=1; \ }while(0) #define EEPROM_READ(addr) ((EEADR=(addr)),(RD=1),EEDATA) #endif /* library function versions */ extern void eeprom_write(unsigned char addr, unsigned char value); extern unsigned char eeprom_read(unsigned char addr); #endif // end EEPROM routines #include "pic.h" unsigned char ReadEE(unsigned char addr); void WriteEE(unsigned char addr,unsigned char data); #define INTCONIT(adr,bit) ((unsigned)(&adr)*8+(bit)) static bit INTCON_7 @ INTCONIT(INTCON,7); unsigned char data = 0x9; void main() { data = 0; WriteEE(0x01,0x99); data = ReadEE(0x01); while(1) { } } //读EEPROM unsigned char ReadEE(unsigned char addr)                //读EEPROM   {     unsigned char num; GIE=0;     while(RD==1);                        //上一次读操作是否完成     EEADR=addr;  //0x01                  //EEPROM地址为00H     EEPGD=0;                             //指向EEPROM数据储存器     RD=1;                                //开始读     while(RD==1);                        //等待读操作完成     num=EEDATA;                          //读出     return(num);                         //返回读出的数   } //写EEPROM void WriteEE(unsigned char addr,unsigned char data)        //写EEPROM   {    while(WR==1);                         //上一次写操作是否完成    EEADR=addr;//0X02                     //EEPROM地址    EEDATA=data; //0X05                   //准备写入EEPROM的数据    EEPGD=0;                              //指向EEPROM数据储存器    WREN=1;                               //使能写操作    GIE=0;                                //禁止中断                                  EECON2=0x55;                          //设置通用参数    EECON2=0xAA;                          //设置通用参数    WR=1;    GIE=1;                                //开始写    while(WR==1);                         //等待写操作完成    if(EEIF==1)      {        EEIF=0;      }                        WREN=0;                               //禁止写操作   }

  • 发表了主题帖: pic16f877 tm0使LED每隔10ms闪亮代码

    /* * File:   main.c * Author: ssais * * Created on 2018年9月19日, 上午8:54 */ #include <xc.h> #define LED RB0 #define TO_10MS 100 char A; void __interrupt() ISR(void); void main() {     TRISBbits_t.TRISB0=0;     OPTION_REG=0b10000101;     INTCON=0b10100000;     TMR0=TO_10MS;     LED=1;A=1;     while(1);     return; } ////中断服务 void __interrupt() ISR(void) {     if (INTCONbits.T0IF==1)     {   INTCONbits.T0IF=0;         TMR0=TO_10MS;         if(A==1)           {A=0;LED=0;}         else           {A=1;LED=1;}     }        }

  • 2019-03-14
  • 发表了主题帖: 如何提高单片机程序执行效率?

          首先什么是执行效率。我们平常所说的执行效率就是使用相同的算法在相同输入条件下完成相同计算所产生的系统开销,目前来说一般会更多关注执行时间方面的开销。所有语言编写的代码最终要运行,都要转化成机器码。在更短的时间内完成相同的事那么效率就高。 关于如何提高C语言程序的执行效率,以我多年的编程经验在这里我来谈谈我的想法: 1.尽量避免调用延时函数 没有带操作系统的程序只能在while(1)里面循环执行,如果在这里面调用大量的延时这样会很消耗CPU的资源,延时等于是让他在这歇着不干事了,只有中断里面的才会执行。如果仅仅是做一个LED一秒闪烁一次的程序,那么很简单,可以直接调用延时函数,但是实际的项目中往往在大循环里有很多事要做,对于实时性要求较高的场合就不行了。为了避免使用延时,可以使用定时器中断产生一个标志位,到了时间标志位置1,在主程序里面只需要检测标志位,置1了才执行一次,然后清标志。其他时间就去做别的事了,而不会在这等待了。最好的例子就是数码管的显示,使用中断调显示,在我们的例程里面有。然后是那个按键检测的,一般的程序都是做的while(!key)等待按键释放,如果按键一直按着,那后面的程序就永远得不到运行死在这了,其实可以做一个按键标志检测下降沿和上升沿就可以避免这个问题了。 2.写出来的代码要尽量简洁,避免重复 在10天学会单片机那本书上看到他写的数码管显示那部分代码,选中一个位,然后送数据,再选中一个位,再送数据,依次做完。代码重复率太高了,不仅占用过多的类存,而且执行效率差可读性差,仅仅是实现了功能而已,实际的编程可以做一个循环,for循环或者while循环。这样的代码看起来更有水平。 3.合理使用宏定义 在程序中如果某个变量或寄存器经常用到,可以使用宏定义定义一个新的名代替他,这样的好处是方便修改,比如液晶的数据端总线接的P1,现在想改到P0,那么只需要修改宏定义这里就可以了,编译器编译的时候,他会自动的把定义的名替换成实际的名称。 4.使用尽量小的数据类型 比如某个变量的值范围是0-255,那么就定义成unsigned char,当然也可以定义成unsigned int,但是这样造成了内存的浪费,而且运算时效率要低一点。如果数据没有负数的话,尽量定义成无符号的类型。应尽量避免定义成浮点型数据类型或双精度(占8个字节)类型,这两种类型运算时很消耗CPU资源。比如采集电压范围是0-5v,精确到小数点后三位,可以把采集到的数据扩大1000倍,即使最大也才到5000,然后多采集几次做个滤波算法,最后电压算出来后只需要在第一位后面加个小数点就可以了,变量定义成unsigned int 型变量就没问题了。 5.避免使用乘除法 乘除法很消耗CPU资源,查看汇编代码会发现,一个乘除法运算会编译出10几甚至几10行代码。如果是乘以或除以2的n次方,可以用<<或>>来实现,这种移位运算在编译时就已经算好了,所以代码很简洁,运算效率就高。但是需要特别注意运算符的优先级问题。 6.尽量使用复合赋值运算符 a=a+b与a+=b 这两个表达式有什么区别呢?前者是先计算a+b的值,然后保存到ACC寄存器,然后再把ACC寄存器的值赋给a,而后者是直接将a+b的值赋给a,节省一个步骤,虽然只节省了一条指令,但是当这个运算循环几千次几万次呢,那么效果很明显了。像其他的-=、*=、/=、%=等都是一样的。 7.尽量不要定义成全局变量 先来看一下局部变量,全局变量,静态局部变量,静态全局变量的异同: (1)局部变量:在一个函数中或复合语句中定义的变量,在动态存储区分配存储单元,在调用时动态分配,在函数或复合语句结束时自动释放; (2)静态局部变量:在一个函数中定义局部变量时,若加上static声明,则此变量为静态局部变量,在静态存储区分配存储单元,在程序运行期间都不释放;静态局部变量只能在该函数中使用;静态局部变量在编译时赋值(若在定义时未进行赋值处理,则默认赋值为0(对数值型变量)或空字符(对字符型变量));静态局部变量在函数调用结束后不自动释放,保留函数调用结束后的值; (3)全局变量:在函数外定义的变量称为全局变量;全局变量在静态存储区分配存储单元,在程序运行期间都不释放,在文件中的函数均可调用该全局变量,其他文件内的函数调用全局变量,需加extern声明; (4)静态全局变量:在函数外定义变量时,若加上static声明,则此变量为静态全局变量;静态全局变量在静态存储区分配存储单元,在程序运行期间都不释放,静态全局变量在编译时赋值(若在定义时未进行赋值处理,则默认赋值为0(对数值型变量)或空字符(对字符型变量));只能在当前文件中使用。 一般情况下就定义成局部变量,这样不仅运行更高效,而且很方便移植。局部变量大多定位于MCU内部的寄存器中,在绝大多数MCU中,使用寄存器操作速度比数据存储器快,指令也更多更灵活,有利于生成质量更高的代码,而且局部变量所的占用的寄存器和数据存储器在不同的模块中可以重复利用。 当中断里需要用到的变量时,就需要定义成全局变量,并且加volatile修饰一下,防止编译器优化。如果数据是只读的比如数码管的断码、汉字取模的字库需要放在ROM里,这样可以节省RAM,51单片机是加code,高级点的单片机都是加const修饰。 8.选择合适的算法和数据结构 应该熟悉算法语言,知道各种算法的优缺点,具体资料请参见相应的参考资料,有很多计算机书籍上都有介绍。将比较慢的顺序查找法用较快的二分查找或乱序查找法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大提高程序执行的效率。. 选择一种合适的数据结构也很重要。 指针是一个包含地址的变量,可对他指向的变量进行寻址。使用指针可以很容易的从一个变量移到下一个变量,故特别适合对大量变量进行操作的场合。数组与指针语句具有十分密切的关系,一般来说,指针比较灵活简洁,而数组则比较直观,容易理解。对于大部分的编译器,使用指针比使用数组生成的代码更短,执行效率更高。但是在Keil中则相反,使用数组比使用的指针生成的代码更短。 9.使用条件编译 一般情况下对C语言程序进行编译时,所有的程序都参加编译,但是有时希望对其中一部分内容只在满足一定条件才编译,这就是条件编译。条件编译可以根据实际情况,选择不同的编译范围,从而产生不同的代码。 10.嵌入汇编---杀手锏 汇编语言是效率最高的计算机语言,在一般项目开发当中一般都采用C 语言来开发的,因为嵌入汇编 之后会影响平台的移植性和可读性,不同平台的汇编指令是不兼容的。但是对于一些执着的程序员要求程序获得极致的运行的效率,他们都在C 语言中嵌入汇编,即“混合编程”。注意:如果想嵌入汇编,一定要对汇编有深刻的了解。不到万不得已的情况,不要使用嵌入汇编。

  • 发表了主题帖: dm8168的DSP和ARM添加库与调用

    在DSP里面调用elf格式的lib库 注:lib库需是elf格式的,用ccs5.2可编译elf格式lib库,lib库如何生成具体见1.1.2 1、把Jupitercar.lib拷贝到 /home/DVRRDK/DVRRDK_04.00.00.03/dvr_rdk/mcfw/src_bios6/alg/va/lib 2、rules_c674.mk里添加imglib2_elf.lib所在的路径 在/home/DVRRDK/DVRRDK_04.00.00.03/dvr_rdk/makerules 下打开rules_c674.mk在里面添加: LIB_PATHS += $(RTSLIB_PATH)  \              $(fc_INCLUDE)/ti/sdo/fc/ecpy/lib/debug/ecpy.ae674 \              $(dvr_rdk_PATH)/mcfw/src_bios6/alg/scd/lib/scd.ae674\              $(dvr_rdk_PATH)/mcfw/src_bios6/alg/va/lib/vlib.ae674\          $(dvr_rdk_PATH)/mcfw/src_bios6/alg/va/lib/Jupitercar.lib\            最后一个$(dvr_rdk_PATH)/mcfw/src_bios6/alg/va/lib/Jupitercar.lib\是我添加进去的。 在ARM里面加linux的动态库.so库 注:这个库可以放任意目录中,只要路径加的正确就ok,(dvr_rdk_PATH)=DVRRDK_03.50.00.05\dvr_rdk,(LIB_DIR)=DVRRDK_03.50.00.05\dvr_rdk\lib\ti816x-evm example: 1、把libcarview.so拷贝到Z:\duth\DVRRDK_03.50.00.05\dvr_rdk\UnitCheck\src 2、看看Z:\duth\DVRRDK_03.50.00.05\dvr_rdk\UnitCheck\src目录下的makefile include $(dvr_rdk_PATH)/makerules/includes_a8.mk $(dvr_rdk_PATH)/makerules/common_header_a8.mk includes_a8.mk是添加库的文件   LIBS= $(LIB_DIR)/dvr_rdk_demo_mcfw_api.a $(MCFW_LIBS) $(AUDIO_LIBS) LIBS= $(LIB_DIR)/UnitCheck.a $(MCFW_LIBS) $(AUDIO_LIBS) $(RPE_LIBS) 看到生成静态库dvr_rdk_demo_mcfw_api.a需要$(MCFW_LIBS) $(AUDIO_LIBS)这2个库 看到生成我们要运行代码的静态库需要$(AUDIO_LIBS) $(RPE_LIBS)这2个库         3、进入includes_a8.mk看到            AUDIO_LIBS=$(linuxdevkit_PATH)/usr/lib/libasound.so.2            MCFW_LIBS=$(LIB_DIR)/dvr_rdk_mcfw_api.a $(LIB_DIR)/dvr_rdk_link_api.a $(LIB_DIR)/dvr_rdk_osa.a $(SYSLINK_LIBS) RPE_LIBS=$(rpe_PATH)/lib/lib/a8/debug/rpe.av5T 接下来加自己的库(仿造上面写) CAR_LIBS=$(dvr_rdk_PATH)/UnitCheck/src/libcarview.so 然后加到自己要生成文件后面 LIBS= $(LIB_DIR)/UnitCheck.a $(MCFW_LIBS) $(AUDIO_LIBS) $(RPE_LIBS) $(CAR_LIBS)

统计信息

已有170人来访过

  • 芯币:4258
  • 好友:--
  • 主题:1403
  • 回复:84
  • 课时:--
  • 资源:--

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言