发帖数

50

原创数

50

关注者

12

阅读数

8744

点赞数

4

蔡琰

  • USB与TTL通信之默契轨道---技术剖析(一)

    话说做这个转换的全隔离小模块也是因为自己做单片机软件设计那么多年,各种通信端口软件都做过,然而需要调试都会用到这些辅助模块来跟上位机通信,这个是最简洁的通信验证方式,那么这些小模块还是很容易坏的,好多时候都会赶在项目节点上突然故障,被迫停下赶紧采购,影响进度不说,还很影响调试产品的心情啊,所以才想去研究设计一个,自己做过的不是用起来也会顺手一些呢。


    首先开始分析实现流程,然后选取集成芯片等工作。

    开始了,那一起看下标题,我想要实现的是转换通信模块,那么需要一个转换芯片;还有就是全隔离,自然需要一个隔离芯片。这是大体方向,细节再慢慢实现下吧。


    1、既然是USB转,那么我直接用USB供电就好了,就不需要再外部供电了

    输入5V电,后端TTL也是5V;输入端加个保险丝,以及后面加的隔离芯片,都是为了保护电脑USB接口。输入端一般我会加一个LED灯,指示用的。

    1.png


    2、输入后的信号就开始进行转换了,我选用了CH340G转换芯片,附件可以自行查看下数据手册啊,如果对你其他项目有帮助可以查看下呢。

    信号线一般需要加个小电阻(10R够了)做防震荡作用,这个芯片工作起来需要一个晶振,晶振的起震电容设计就不用多说了吧,电源端部分还要有储能和滤波电容,这个也不用多说了吧,那么一起来看下转换端部分的设计原图:

    2.png


    3、信号转换后就是进行全隔离了,前面加保险丝的时候说明了隔离的作用,就是保护,有些时候我们也知道如果没隔离,对于我们的USB接口还是有不确定性的,接入的模块你不确定会不会有干扰,什么干扰,我觉得还是很有必要对这些频繁用到的模块加隔离比较稳妥,个人见解。

    对于隔离,除了信号还有电源和地的隔离,这才叫全隔离,那上图看下吧。

     

    3.png

    4.png

    另外输出部分有电源、地、信号发送接收了,输出是5V输出能力100mA,如果目标板电流过大,建议使用外部独立电源供电,使用外部独立供电时可不接5V,但必须要接GND,这个是要注意的。

    主要部分的设计就完成了,是不是觉得其他我们平时买的小模块内容也没想象中那么复杂,其实做任何设计都要选定芯片研读芯片手册,不管做软件设计也好,硬件设计也好,只是关注点不一样。当然还有一些是经验值。

    当我们熟悉了这些模块的设计,即便买来的碰到故障了不是很容易分析处理一下的,最起码不会影响工作进度的。多学点总归没错的。


    后面就开始设计PCB过程了,最后产品出来就可以通信使用了,每个过程都会有收获的,如果大家感兴趣的话就持续关注我吧。后面会陆续分享出我的设计视频过程,随时欢迎大家跟我来探讨,也让我可以开阔下思路,或许还有其他实现方式,不同的尝试过程才是经验积累成长的过程。


    收藏 0 回复 0 浏览 114
  • BMS电池管理系统

    大家好!我是张飞实战电子蔡琰老师,今天给大家分享BMS电池管理系统


    随着新能源、可穿戴设备等的发展,BMS成为热议的话题,经久不衰,这篇文章我们就一起来学习一些关于BMS的知识。



    Q1.什么是BMS?

    BMS是对电池进行监控和管理的系统(Battery Management System),通过对电池的电压、电流、温度、剩余容量(SOC)、放电功率,报告电池劣化程度(SOH)、电池均衡管理、报警提醒等参数采集、计算进而控制电池放电过程,实现对电池的保护,提升电池综合性能的管理系统。它还根据电池的电压电流及温度用算法控制最大输出功率以获得最大续航,用算法控制充电机进行最佳电流的充电,它通过通信线与其他控制单元进行通信,比如我们手机或新能源汽车的快充、电量显示等都与BMS有着密切的关系。



    Q2.动力电池和非动力电池区别?

    提到电池,一般被分为动力电池和非动力电池,新能源汽车的动力来源一般主要是以动力电池为主。一般,动力电池实际上就是为交通运输工具提供动力来源的一种电源。它与普通电池的主要区别为动力电池相比于普通电池,其放电功率大。它可以在很短的时间内将电池的电放完。动力电池一般容量要小于非动力电池。



    Q3.什么是充放电倍率?

    一般电池后面的xxC它表示了电池的放电能力,C:用来表示电池充放电电流大小的比率,即倍率。充放电倍率=充放电电流/额定容量,例如1000mAh的电池,1C放电,就是1A放电,那么1个小时就可以放完,2C放电,就是2A放电,半个小时就可以放完。当然不同材质的电池,放电能力也是不一样的。



    Q4.什么是均衡控制?

    生产制造和使用过程的差异性,造成了动力电池单体天然就存在着不一致性。不一致性主要表现在单体容量、内阻、自放电率、充放电效率等方面。单体的不一致,传导至动力电池包,必然的带来了动力电池包容量的损失,进而造成寿命的下降。


    根据木桶短板效应,充电和放电时都是性能最差的单体先达到截止条件,其他还有一部分能力并未释放出来,这样就造成了浪费。


    电池单体的不一致,会随着时间的推移,在温度以及振动条件等随机因素的影响下进一步恶化,趋势无法逆转,但可以干预,降低它的恶化速率。方法之一就是通过电池管理系统对电芯实施均衡。


    均衡包括主动均衡和被动均衡,被动均衡,运用电阻器,将高电压或者高荷电量电芯的能量消耗掉,以达到减小不同电芯之间差距的目的,是一种能量的消耗。主动均衡,运用储能器件等,将荷载较多能量的电芯部分能量转移到能量较少的电芯上去,是能量的转移。


    但是均衡也存在一定的局限性,被动均衡,电流无法完全按照实际需求去做,因为通过电阻消耗的能量,转化成热量,对电池管理系统以及电池包都会产生不良影响;主动均衡,需要配置相应电路和储能器件,体积大,成本上升,这两个条件一起决定了主动均衡不容易推广应用。电池包的每个充电放电过程,都伴随着一部分电池局部的附加充放过程,无形中增加了电池的循环次数。



    Q5.一般BMS管理系统是什么样的?

    在一些系统中对成本的要求比较高,比如手扶电动两轮车,在这个系统中BMS电池管理系统设计也相对简单一些,仅由一颗专用的管理芯片即可实现电池管理,当然这个管理是纯硬件的,没有加入软件,实现一些基本的功能:正常充放电、过充过放、温度保护、平衡等。


    在复杂的管理系统中,从系统层次来进行架构管理,会引入一些控制单元(如单片机)做一些控制算法,它不仅包含硬件,还包含底层软件、应用层软件等。

    image.png

    如上图(来源于网络),分为主控,从控,电压电流采集控制、仪表显示等功能,非常复杂。单单这一个系统里面用到的单片机数量是十分可观的。


    上面我们简单对BMS一些问题进行了了解,作为控制工程师,首先学好单片机是非常重要的,还等什么,赶紧行动起来吧!


    收藏 0 回复 0 浏览 114
  • 程序的调试和宏使用的技巧

    在程序的开发过程中,调试语句是程序开发的一种主要的辅助手段。C语言主要的调试语句使用的是printf,它的定位是系统的标准输出。在嵌入式系统中,printf()的输出可能是屏幕、也可能是串口。


    #字符串转化操作符

    在编译系统中,可以使用#将当前的内容转换成字符串,例如:

    #define dprint(expr)  printf(<main>%s = %d n,#expr,expr)


    ①在程序中可以使用如下的方式调用:

    1.jpg

    在以上的例子中,使用#expr表示根据宏中的参数(即表达式的内容),生成一个字符串。因此,#expr代表将dprintexpr)括号中的内容生成一个字符串。一般来说,宏中的参数将作为一个变量被引用,而增加了#修饰之后的表达式,即代表了将宏中的参数名称直接转换成字符串。

    上述过程同样是由编译器产生的,编译器在编译源文件的时候,如果遇到了类似的宏(示例中的dprint)会自动根据程序中表达式的内容,生成一个字符串的宏(示例中的#expr)。这样宏同样可以在程序中表示一个字符串。


    ②进一步,在程序中可以按照如下的形式调用以上宏:

    2.jpg

    从运行结果的第一行可以看到,编译器的生成字符串的时候,不会照搬宏参数中的所有内容,注释类的内容是不会被放入字符串的宏,这也是因为去注释是编译器预处理阶段的内容,也就是说在实际的编译过程之前,程序中的注释已经被去掉。从运行结果的第二行看出,由于a不是整数,而是字符串的指针,因此打印出a的值实际是变量a的地址,而字符串的内容依然是a。从运行结果的第三行看出,对于直接写入程序的数值(立即数),编译器也可以将它的内容转换成字符串。


    这种方式的优点是可以用统一的方法打印表达式的内容,在程序的调试过程中可以方便直观地看到转换成字符串之后的表达式。具体的表达式的内容是什么,是由编译器“自动”写入程序中的,这样使用相同的宏打印所有表达式的字符串。


    由于#expr本质就是一个表示字符串的宏,因此在程序中也可以不使用%打印它的内容,而是可以将其直接和其他的字符串连接。上面的宏可以等价为以下的形式:

    #define dprint (expr)  printf(<main> #expr =%d n,expr)

    注意:#C语言预处理阶段的字符串转化操作符,可以将宏中的内容转换成字符串。


    ##:连接操作符

    在编译系统中,##C语言中的连接操作符,可以在编译的预处理阶段实现字符串连接的操作。

    以下的程序是一个使用##的示例:

    #define test(x) test ## x

    void test1(int a)      void test2(char *s)

    {         {

    printf(Test 1 interger: %d n,a);   printf(Test 2 String : %s n,s);

    }         }

    3.jpg

    在上面这个程序,test(x)宏被定义为test##x,它表示test字符串和x字符串的连接,因此test(1)将被预处理器为:test1,test(2)将被预处理器处理为:test2。预处理器仅仅是转换字符串而已,所以上面的test1test2刚好转换成两个函数的名称。


    条件编译调试语句


    在嵌入式系统的调试中,调试语句可以在程序运行的过程中输出程序的运行状态。然而调试语句的调用是有开销的,在最终发布版的程序中,调试语句都是应该去掉的。去掉调试语句最简单的方式将其注释掉,但是主要就需要维护两种源程序:一种是带有调试语句的调试版程序,另一种是不带有调试语句的发布版程序。这显然不是一种很好的方式,理想的方式是只有一套源程序,根据不同的条件编译选项,编译出不同的调试版和发布版的程序。


    在实现的过程中,可以使用一个调试宏来控制调试语句的开关,如下所示:

    #ifndef USE_DEBUG

    #define DEBUG_OUT(fmt,args...) printf(File:%s Function:%s Line:%dfmt,_FILE_,_FUNCTION_,_LINE_,

    ##args)

    #else

    #define DEBUG_OUT(fmt,args...)

    #endif

    在上面的程序中,宏USE_DEBUG用来控制调试语句的开关,当USE_DEBUG被定义的时候,将调试语句DEBUG_OUT定义成上面部分的形式,当没有定义的时候,宏定义为空,在这种情况下,即使程序中写很多DEBUG_OUT,编译器也会将其处理为没有任何语句。

    注意:一条语句太长换行需要在每行的结尾使用,表示下一行的内容是和上面的连续的。


    使用do...while的宏定义


    使用宏定义可以将一些较为短小的功能封装,方便使用。宏的形式和函数类似,但是可以节省函数跳转的开销。如何将一个语句封装成一个宏,在程序中常常使用do...while(0)的形式,例如,对一个简单打印的语句的宏封装如下所示:

    #define HELLO(str)  do{ printf(hello:%sn,str); }while(0)

    在上面这个语句中,将实际执行的功能封装在一个do...while(0)循环内。事实上,do...while(0)由于条件不成立,因此循环体之间的语句只会执行一次。然而,这样做的好处是就是可以让do...while(0)之中的语句像函数一样使用,而不必担心编译器发生错误。


    如果直接把后面语句放入宏使用,不用do...while,那么宏在一般顺序执行语句中使用没有问题,如果使用在if...else中,都会发生错误编译。事实上,一般的语句中多一个分号,只相当于多了一条空语句,没有影响。这里却是在if语句后面多出一个分号,它们代表if语句的结束。因此,后面的else就会被视为一条新的语句(相当于前面没有if只有else,这就会发生编译错误。


    而如果使用do...while(0)的形式就没有以上的问题,而且一般的C语言编译器都会对do...while(0)进行优化,使其和一般的一条函数等价,在其中可以含有任意条语句。

    收藏 0 回复 0 浏览 113
  • C语言中的动态内存-----栈内存

    C语言程序的动态内存分为栈内存区域和堆内存区域两种。栈内存是由编译器管理的,而堆内存是由程序调用具体的库函数管理的。我们今天分析下栈内存的概念。


    栈内存的使用在很大程度上依赖于处理器的硬件机制。在处理器中,一般有一个寄存器来表示当前栈指针的位置,通常在内存中分配一块区域,这块内存的上界(高内存地址)和下界(低内存地址)之间是可用的栈内存区域。


    栈指针是一个指向栈区域内部的指针,也就是它的值是一个地址,这个地址位于栈区的下界和栈区的上界之间。栈指针把这个栈区域分为两个部分,一个是已经使用的区域,一个是没有使用的区域。


    对于栈内存的增长方向有两种:一种是向上增长的,也就是低地址向高地址增长;另一个是向下增长的,高地址向低地址增长。在目前常见的体系结构和编译系统中,栈大多是向下增长的,我们也是看下这种常见的增长形式。在初始阶段,栈指针是指向栈区间的上界。随着栈使用量的增加,栈指针的值将向低地址移动,也就是在变小。


    栈内存在使用过程中有一个重要的特性是先入后出,也就是后入栈的内容将先出栈,而先入栈的后出栈。类似于一个口的瓶子,先进去的在底下,要想底下的出来就先把上面的先倒出来。栈内存的使用情况见下图:


    1.jpg

    入栈的过程和出栈的过程我们安全用图形来表示,更形象些吧~

    2.jpg


    在入栈的过程中,如果栈指针的变化超出栈内存的区域,将发生栈溢出。

    从图中看出栈指针的功能是标识当前的栈位置。对栈内存处理中,每次能够获取的内容都是最后可放入栈内存的内容,而每次放入栈内存中的内容都将位于栈区域的最后。


    总的来说其实栈是一个先入后出的内存区域,栈指针是提供一种硬件的内存机制。


    还有一个大家可能都没听说过,或者都没关注过的,我们来一起了解一下,就是满栈和空栈的概念,我们还是通过图来形容一下,这个是由处理器的体系结构决定的。与程序的编写没有关系,甚至编译器都不需要关注这个问题。无论在哪种情况下,栈指针都是已经使用的栈区域和未使用的栈区域的分界线。


    3.jpg

    在满栈的情况:栈指针当前的位置是已经使用的栈区域。

    在空栈的情况:栈指针当期的位置是没有使用的栈区域。


    这个仅供大家了解下就可以了,毕竟对于我们大多数人来说都是应用者,多了解点底层的总没错,但也不必太深挖。对于栈内存的概念我就分享到这里,其实这个对于写汇编的人来说就很有用处了,或者去多读一些汇编就很能清楚栈内存的妙用了。后续我分享堆内存的一些概念,话说知识是一点点积累的过程,有时候觉得前面有的知识点懵懵懂懂的突然连起来就又通透了。这就是坚持学习的作用,希望大家都能坚持多学,才能更会用。

    收藏 1 回复 0 浏览 101
  • 堆内存的那些事

    上一篇我们分享了栈内存的概念,现在我们分享下堆内存的概念。


    在一般的编译系统中,堆内存的分配方向和栈内存是相反的。当栈内存从高地址向低地址增长的时候,堆内存从低地址向高地址分配。

    C语言中,堆内存在分配和释放的时候,是程序通过调用C语言的库函数完成的。这和栈内存的分配有区别,栈内存利用的是处理器的硬件机制,而堆内存的处理使用的是库函数。


    我们来看下堆内存的分配情况:

    1.jpg

    在堆内存的分配过程中,每次分配将返回一个当前分配地址的指针。在程序中如果多次分配内存,可以得到多个内存指针,每个内存指针都是本次分配内存的地址。在释放内存的时候,只需要对每个指针进行操作,那个指针所指向的内存就会被释放,而对其他的内存区域没有影响。


    从内存的分配和使用上,可以看出栈内存和堆内存的区别:栈内存只有一个入口点,就是栈指针,栈内存压入和弹出的时候栈指针将发生变化,栈指针标识当前栈区域中已使用和未使用的界限,程序在访问栈内存的时候都只能通过栈指针及其偏移量;而堆内存有多个入口点,每次分配得到的指针是访问内存的入口,每个分配内存区域都可以被单独释放,程序对堆内存可以通过每次分配得到的指针访问。


    堆内存有一个整体分配的过程,按照向上的堆内存分配方向。随着堆内存使用量的增加,堆内存将逐渐向高地址分配。这只是一个大体的增长的方面,在堆内存中,已使用的区域和未使用的区域是交错的,而不是像栈区域那样有明显的分界线。

    堆内存的释放看下面这个图:


    2.jpg

    看到这样频繁的使用区域和释放,那么很容易看出堆内存是不连续的,跟堆内存的使用方式有关系,这个分配就相对自由灵活了,但是也是会在低地址向高地址发展的方向分配的。


    比如上面释放后再分配就可以是下面两种情况:

    3.jpg

    先看再次分配1的情况:当新分配的需求比中间(刚刚释放)区域小,那么就会在紧接着的区域给分配。


    再看再次分配2的情况:当新分配的需求比中间(释放的)区域大,那么只能往后寻求能给的区域。

    当频繁的分配和释放内存的过程中,会很容易出现在两块已经分配的内存之间较小的未分配内存区域,这些其实可以用,但是由于他们的空间比较小,不够连续内存的分配,所以分配的时候就很难再次使用,这些较小的内存就是我们常说的内存碎片。


    我们再来聊一下在C程序中堆空间的使用。

    C语言中,堆内存区域的分配和释放是通过调用库函数来完成的,实现的函数主要有四个:

    void *malloc(size_t size);    //分配内存空间

    void free(void *ptr);     //释放内存空间

    void *calloc(size_t nmemb,size_t size);  //分配内存空间

    void *realloc(void * ptr,size_t size);  //重新分配内存空间

    注意:使用上面这几个函数需要包含标准库文件 <stdlib.h>

    那么库函数怎么使用呢,内存分配了就要有释放,那么常用的就是malloc()free()两个函数。malloc()函数的输入是需要分配内存的大小,输出是分配内存的指针。如果分配不成功,则返回NULL


    free()函数的输入是需要释放的指针,可以接受任何形式的指针。这个指针必须是由分配函数分配出来的。

    例如:

    int *pa;

    pa = (int *)malloc(sizeof(int));//分配一个int大小的指针

    if(NULL != pa)

    {

    free(pa);

    }

    内存使用完成需要释放,以便分配给其他程序使用。


    calloc()也是内存分配的,只是可以把分配好的内存区域的初始值全部设置为0。还有这个分配内存有两个参数,第一个是分配单元的大小,第二个是要分配的数目。

    malloc(sizeof(unsigned int)*10);   ==     calloc(sizeof(unsigned int),10)


    realloc()有两个参数,一个是指向内存的地址指针,一个是要重分配内存的大小,返回值是指向所分配内存的指针。

    1、当参数指针为NULL的时候,作为malloc使用,分配内存

    2、当重分配内存大小为0的时候,作为free使用,释放内存

    3、当指针和重分配内存大小均不为0的时候,根据指针指向的堆内存区域的情况和指针大小重新分配内存。


    对于realloc()作为重新分配内存的时候,有三种可能出现:

    1、缩小内存

    2、扩大内存,不需要移动指针

    3、扩大内存,需要移动指针(指定内存区域大小不够)


    在堆内存的管理上,主要容易出现以下几个问题:

    1、开辟的内存没有释放,造成内存泄漏(系统不会释放任何用户分配的内存)

    2、野指针被使用或释放(内存释放后,需要将内存指针置为NULL

    3、非法释放指针(分配了有效内存才存在释放,否则是非法的)


    C语言语法的方面对栈内存和堆内存如何使用没有限制。然后从使用的角度,栈内存更适用于容量较小的单个变量(例如:C语言的基本变量类型、较小的结构体和数组),堆内存则适用于开辟较大块的内存。栈内存由编译器分配和释放,堆内存由程序员分配和释放。

    收藏 0 回复 0 浏览 101
×
蔡琰