• STM32L0芯片FLASH编程示例及提醒

    来源 | 茶花MCU 这里就STM32L053芯片的FLASH编程做个简单演示并做些提醒,以供有需要的人参考。 一般来讲,FLASH编程主要包括擦除、代码编程、Option字修改操作,关于Option编程下面不做介绍。 STM32L0芯片的擦除除了支持全片擦除外,再就是支持页擦除,每页的大小为128Bytes,即32个字。编程可以按字或按半页【64Bytes】编程。 单页擦除、单字编程以及半页编程的时间都是一样的,大概3.2ms左右,这点在芯片数据手册上也明确出来了。 这里提醒两点:第一点,在做擦除或编程时,要注意地址对齐的问题,页擦除时地址要128字节对齐,字编程注意4字节对齐,半页编程时注意64字节对齐;还有一点就是做半页编程时,半页编程的执行代码要放到RAM里进行,这点手册也强调了。 下面演示字编程、页编程、页擦除的操作。 这里我先以字编程模式写5个字,然后以半页编程模式对5个半页进行FLASH编程,并记录二者所花的时间,看看字编程时间跟半页编程的是否一致。 另外,在完成5个半页编程之后,又进行了页擦除操作,擦除刚才已编程的5个半页中的1页,即最后应只剩下3个半页的内容【注:对于STM32L0系列芯片,内部FLASH被擦除后内容为全0】。 下面代码截图是基于STM32Cube库来组织的,主要涉及到字编程、半页编程、页擦除三个操作,对应着绿色下划线的3个库函数。 其中,半页编程的执行代码需配置到RAM里去运行。 另外,Period1和Period2分别来存放写5个字和5个半页的编程时间,并放在指定的FLASH位置。 编译运行后我们可以看到如下结果: 上面截图是经过运行后芯片内部的部分FLASH空间的内容。5个红色方框围住的数据乃5个字编程后的结果,蓝色方框内的数据乃5次半页编程后的结果,但最终只看到3个半页的编程内容,那是因为后面两个半页的内容经页擦除操作后而消失了。 用来统计编程时间的定时器的计数频率为1MHz,显然Period1和Period2基本是相等的,将它们再除以5后所得编程时间都是3.3ms的样子。显而易见,进行批量代码编程时采用半页编程更高效。 前面说了做半页编程时其执行代码需放到RAM运行,该代码在STM32cube库的这个文件stm32l0xx_hal_flash_ramfunc.c里面。实现该操作对于不同的IDE在处理上稍有差异。这里基于ARM MDK进行简单配置,划分点RAM出来给它用。 关于STM32L0系列FLASH编程的演示就介绍到这里,愿能帮到有需之人以节省些时间和精力。 ------------ END ------------ 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    strongerHuang STM32L0 芯片 Flash 编程

  • 前几天哪位老哥让我推荐C语言书籍来着?

    大家好,我是小麦,就在前几天,有几位老哥和我私聊,让我整理一份C语言经典书籍的书单,现在我把这个清单重新整理了一下,这里有零基础入门和进阶提高的书籍,对每个阶段的学习都会有帮助,下面它来了。 关于C语言 先看C语言的历史;1973 年,KenThompson 和 DennisRitchie 在做系统内核移植开发时,感觉使用汇编语言很难实现。后来决定使用一种称为 BCPL的语言进行开发,在开发过程中,他们在 BCPL 的基础上做了进一步的改进,推出了 B 语言(取 BCPL 第一个字母)。 后来发现使用 B 语言开发的 UNIX 内核,还是无法达到他们的预期要求,于是在 B 语言的基础上,做了进一步的改进,设计出了具有丰富的数据类型,并支持大量运算符的编程语言。改进后的语言较B语言有质的飞跃,1970年左右,取名为 C 语言,并使用 C 语言成功重新编写了 UNIX内核。 这也是为什么UNIX的时间戳默认是从1970年1月1日开始; C/C++无处不在,到底能做哪些事情呢? 大多数操作系统内核都是用C编写的,包括但不限于Windows,Linux,Mac,iOS,Android等。 现代浏览器也是用C/C++编写的。像Chrome,Firefox等。 现代游戏引擎是用C/C++编写的,例如Unity3D,虚幻引擎,cocos2d-x等。 编程语言的编译器和解释器也是用C/C++实现的。 下面给大家推荐基本C语言学习非常优秀的书籍。 C Primer Plus Linux C编程一站式学习 C 和指针 C 程序设计语言 深入理解C指针 C 专家编程 C 陷阱与缺陷 C 语言的科学和艺术 C 语言程序设计现代方法 C 语言接口与实现 数据结构与算法分析——C语言描述 UNIX环境高级编程(第3版) Linux程序设计 C Primer Plus 《C Primer Plus(第5版)(中文版)》是C语言书最好的入门书籍之一,0基础完全可以。 可以说是满分入门书籍,内容循序渐进,这本书重要的不止是让你学会了C语言,更重要的是能够锻炼你的编程思想,这对以后的学习很有帮助。 遇到看不懂的地方多看几遍,再看不懂就先跳过,有时候一回头就突然懂了。 如果想把C当作吃饭的技能,除了这本书,还必须要再补一下数据结构和算法方面的知识。 Linux C编程一站式学习 本书有两条线索: 一条线索是以Linux平台为载体全面深入地介绍C语言的语法和程序的工作原理; 另一条线索是介绍程序设计的基本思想和开发调试方法。 本书分为两部分: 第一部分讲解编程语言和程序设计的基本思想方法,让读者从概念上认识C语言; 第二部分结合操作系统和体系结构的知识讲解程序的工作原理,让读者从本质上认识C语言。 本书适合做零基础的初学者学习C语言的第一本教材,帮助读者打下牢固的基础。 有一定的编程经验但知识体系不够完整的读者也可以对照本书查缺补漏,从而更深入地理解程序的工作原理。 本书对于C语言的语法介绍得非常全面,对C99标准做了很多解读,因此也可以作为一本精简的C语言语法参考书。 C 和指针 这本书和《专家编程》《C缺陷和陷阱》可以并称C语言(进阶书)三杰; 这本书提供与C语言编程相关的全面资源和深入讨论,由浅入深; 它涵盖了C语言的全部内容,特别注重指针的讲解,除了头尾的几章,指针的话题几乎是贯穿了全书。 正是指针使得C语言如此之强大,所以要学习C语言的精髓,就是要精通指针! C 程序设计语言 在计算机发展的历史上,没有哪一种程序设计语言像C语言这样应用广泛。 本书作者是C语言之父,相当经典,“hello,World"程序就是由本书首次引入的。 不过读这本书,我们得有一些unix like系统的操作经验,需要知道文件描述符,输入输出流,重定向,管道以及”anything is file“等在unix世界里这些归为常识的概念; 这些离我们这些在windows的世界里长大的一辈太远。 深入理解C指针 深入理解C指针和内存管理,提升编程效率!这是一本实战型图书,通过它,读者可以掌握指针动态操控内存的机制、对数据结构的增强支持,以及访问硬件等技术。 C 专家编程 虽然是技术类书籍,但是作者很幽默,书里面八卦比较多,趣味性比较强; 同时也展示了优秀的C程序员所使用的编码技巧,并专门开辟了一章对C++的基础知识进行了介绍。 对于有一定经验的C程序员会很有帮助; 对于C语言功底深厚的程序员,本书可以帮助他们站在C的高度了解和学习C++。 C 陷阱与缺陷 作者以自己1985年在Bell实验室时发表的一篇论文为基础,结合自己的工作经验扩展成为这本对C程序员具有珍贵价值的经典著作。 写作本书的出发点不是要批判C语言,而是要帮助C程序员绕过编程过程中的陷阱和障碍。 本书适合有一定经验的C程序员阅读学习,即便你是C编程高手,本书也应该成为你的案头必备书籍。 正如书上所说,“本书所揭示的知识,至少能够帮助你减少C代码和初级C++代码中90%的Bug”,我觉得这并不夸张。 C 语言的科学和艺术 《C语言的科学和艺术》是一本C语言经典教材,强调软件工程和优秀的程序设计风格。 此外,读者还可以从书中学习到ANSIC的基础知识,这些内容已经成为计算机行业的标准。 作者的写作风格使得书中深奥的概念变得易于理解和引人入胜。 这本书集中讨论库和抽象的用法,这是当代程序设计技术中最基本的知识。 使用库来隐藏C语言的复杂性,更加突出主题,使读者可以较好地掌握每一个主题的精髓。 然后,进一步给出每个库的底层实现,较好地展示了库自身的抽象威力。 C 语言程序设计现代方法 《C语言程序设计现代方法》最主要的一个目的就是通过一种“现代方法”来介绍C语言,实现客观评价C语言、强调标准化C语言、强调软件工程、不再强调“手工优化”、强调与c++语言的兼容性的目标。《C语言程序设计现代方法》分为C语言的基础特性。C语言的高级特性、C语言标准库和参考资料4个部分。每章都有“问与答”小节,给出一系列与本章内容相关的问题及其答案,此外还包含适量的习题。 C 语言接口与实现 《C语言接口与实现:创建可重用软件的技术》概念清晰、实例详尽,是一本有关设计、实现和有效使用C语言库函数,掌握创建可重用C语言软件模块技术的参考指南。 书中提供了大量实例,重在阐述如何用一种与语言无关的方法将接口设计实现独立出来,从而用一种基于接口的设计途径创建可重用的API。 数据结构与算法分析——C语言描述 本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。 原书曾被评为20世纪顶尖的30部计算机著作之一,作者Mark Allen Weiss在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界500余所大学用作教材。 在本书中,作者更加精炼并强化了他对算法和数据结构方面创新的处理方法。通过C程序的实现,着重阐述了抽象数据类型的概念,并对算法的效率、性能和运行时间进行了分析。 UNIX环境高级编程 《UNIX环境高级编程》被誉为UNIX编程“圣经”。 经典中的经典。不过看这本书的前提是你熟悉linux,哪怕不是使用linux接口编程,至少要用过,了解shell,gcc,vim。所以适合有一些基础的读者。 Linux程序设计 时至今日,Linux系统发展越来越成熟,因为具备跨平台、开源、支持众多应用软件和网络协议等优点,它得到了各大主流软硬件厂商的支持,也成为广大程序设计人员理想的开发平台。 本书是Linux程序设计领域的经典名著,以简单易懂、内容全面和示例丰富而受到广泛好评。 中文版前两版出版后,在国内的Linux爱好者和程序员中也引起了强烈反响,这一热潮一直持续至今。 —— The End —

    小麦大叔 C语言 编程

  • 6个最佳的人工智能开发框架和AI库

    随着公司积累大量数据并寻找合适的技术进行分析和利用,人工智能(AI)逐渐成为主流。这就是为什么Gartner预测到2021年80%的新兴技术将拥有AI基础。 随着预测分析,机器学习和其他数据科学的趋势已经开始,营销人员需要开始关注如何利用这些技术来形成以数据为驱动力的营销策略。考虑到这一点,我们询问了AI行业专家,为什么营销领导者需要开始考虑AI,以及一些最好的开源AI框架来保持关注。 这里有6个最受欢迎的创新开源AI框架。 1. TensorFlow TensorFlow是一个由工具,库和资源组成的生态系统,许多受欢迎的公司(如Airbnb,eBay,DropBox等)都在使用它。TensorFlow旨在简化和简化机器学习算法的复杂性以简化开发。使用视觉模型和流程图,开发人员和数据科学家可以快速创建神经网络和其他机器学习模型来利用数据。例如,Airbnb正在使用TensorFlow对公寓列表中的照片进行分类,以确保它们准确代表特定的空间。 2. 亚马逊 SageMaker Neo 亚马逊最近将其机器学习平台的功能Amazon SageMaker Neo开源,作为服务产品。新发布的Neo-AI项目代码将使AI开发人员能够训练机器学习模型并在云中的任何地方运行它们。Neo-AI项目针对需要快速和低延迟预测的边缘计算设备和物联网(IoT)传感器进行了优化。 例如,专门从事数字娱乐产品的公司先锋公司(Pioneer Corp)使用Amazon SageMaker Neo进行实时图像检测和汽车内摄像头的分类。同样,野村综合研究所(NRI)正在使用Amazon SageMaker Neo来检测便利商店,机场和其他企业中安装的相机中的物体,以优化运营。 3. Scikit-learn Scikit-learn是一个基于Python的开源机器学习库,专注于数据挖掘和分析。它建立在NumPy,SciPy和matplotlib之上,并具有精选的一组高质量的机器学习模型,可用于最受欢迎的用例。Morgan和Evernote等知名品牌使用Scikit-learn进行预测分析,个性化推荐和其他数据驱动的任务。 4. Microsoft认知工具包 Microsoft认知工具包(CNTK)是一个开源的深度学习框架。 CNTK可以以各种语言的库形式包含在项目中,也可以通过自己的称为BrainScript的模型描述语言用作独立的机器学习工具。 Bing,Cortana和其他品牌的商业级工具包使用的海量数据集需要可扩展且高度优化的机器学习平台。 5. Theano Theano是与NumPy紧密集成的深度学习Python库。这意味着其主要用例是使用相对简单的Python脚本定义和评估复杂的数学表达式,同时利用高级计算来优化性能。即便如此,Theano仍被认为是低级框架,许多品牌选择使用在其之上构建的框架如Keras或Blocks。 6. Keras Keras是可以在TensorFlow,Microsoft Cognitive Toolkit和Theano之上运行的高级机器学习API。它的易用性和对开发人员体验的关注使Keras成为快速制作新应用程序原型的首选。 Netflix,Uber和Yelp等许多品牌以及规模较小的创业公司已将Keras集成到其核心产品和服务中。   免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    玩转嵌入式 人工智能 AI 开发框架

  • 如何写模块化的代码?

    随着我们工程化经验的增加,不知不觉的我们就会关心到这个问题,模块化,模块设计就显现出来,那到底什么是模块化呢? 这不叫模块化 我相信在很多时候,我们刚开始从零开始接手一个项目的时候,编码之前总想着要实现什么的功能需要的模块,然后要程序 模块化,这种思想是值得认同的,但往往我们并没有做到真正的模块化。 例如在一个需要很多外设接口,一般需要硬件初始化,相关配置,中断服务程序,输入输出,逻辑处理等功能,我们的做法可能就是把代码分布到多个文件和目录里面,然后把这些目录或者文件取名 xxxModule。 甚至把这些目录分放在不同的仓库目录里面,结果随着编码的增加,发现好多小功能都重复了,或者本可以写在一起的函数并没有放在一起,导致我们的代码思想不是很流畅,这样做会误导我们,甚至整个项目实现的思路。 究其原因这是因为我们其实并不理解什么叫做 模块,而仅仅是肤浅的把代码切割开来,分放在不同的位置,虽然这确实达到了部分模块化的目的,但是也会制造一些不必要的麻烦。 什么是真正的模块化? 真正的模块化,并不是简单文本意义上的,而是与逻辑相关的有逻辑意义的。一个模块应该像一个集成电路芯片,我们能见到能使用的都很清晰,它定义了良好的输入和输出。 模块是可能分开地被编写的单位。这使他们可再用和允许广泛人员同时协作、编写及研究不同的模块。 实际上,编程语言已经为我们提供了一种很好的模块化方法,它的名字叫做 函数。每一个函数都有明确的 输入(参数)和输出(返回值),同一个文件里可以包含多个函数,所以你其实根本不需要把代码分开在多个文件或者目录里面,同样可以完成代码的模块化。 按照函数这个原则,我可以把代码全都写在同一个文件里,却仍然是非常模块化的代码,是不是觉得与之前的想法不一样? 是的,软件编程模块是一套一致而互相有紧密关联的软件组织, 每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体,完成整个系统所要求的功能,这就是真正的模块化。 怎么模块化? 我们知道了模块化的原则与道理之后,就可以按照这个思路去开发项目了,想要达到很好的模块化,你需要做到以下几点。我们从实现角度来说。 避免写太长的函数 一眼望去,如果一个函数的代码你电脑一页都看不完,那肯定是冗长了或者不符合模块化编程了。不需要滚屏就可以看得到全部的函数内容,那对我们的理解也有帮助。 一般来说一个函数最好不要超过40行代码(当然这个不是死规定,只是一个经验建议而已),如果写的函数太大了,就应该把它拆分成几个更小的函数。 也许你会说到,这很难办到,逻辑很多或者很多判断条件的时候,40行往往不够吧,那么其实我们也需要考虑到函数里面一些复杂的部分,是不是可以提取出来,单独写一个小函数,再从原来的函数里面调用。 另外函数层级也不要太多,比如一个函数长成这样: 1void function(void* para) 2{ 3 if (getOS().equals("MacOS")) { 4 a(); 5 if(getOS().equals("AndroidOS")){ 6 b(); 7 if(getOS().equals("flymeOS")){ 8 c(); 9 } 10 } 11 } 12} 我们看到这个函数由于很多的判断,函数层级已经超过4层了,这其实对我们的理解很不利,另一方面,一些逻辑变化也会导致我们的更改很麻烦,费脑子。 每个函数只做一件简单的事情 有些人喜欢写一些 通用函数,一般我都放在 publicModule里面,既可以实现这个工又可以实现那个功能,它的内部依据某些变量或者是某些条件,来选择这个函数所要实现的小功能。 比如,写出这样的函数: 1void function() { 2 if (getOS().equals("MacOS")) { 3 a(); 4 } else { 5 b(); 6 } 7 8 c(); 9 10 if (getOS().equals("MacOS")) { 11 d(); 12 } else { 13 e(); 14 } 15} 这个函数,是想表达根据系统是否为MacOS,从而来做不同的事情。从这个函数可以很容易的看出,其实只有函数c()是两种系统共有的,而其它的函数 a(), b(), d(), e()都属于不同的分支。 但是这种复用的写法其实是很不利的。如果一个函数可能实现2个功能,并且它们之间 共同点少于它们的不同点,那我们最好就写两个不同的函数,否则这个函数的逻辑就不会很清晰,容易出现错误。 不要害怕,函数简单点不丢人,我们不需要炫技。 好了,根据上面的说法,这个函数可以改写成两个函数: 1void funMacOS() { 2 a(); 3 c(); 4 d(); 5} 和 1void funOther() { 2 b(); 3 c(); 4 e(); 5} 如果我们发现两件事情大部分内容相同,只有少数不同,也就是说 共同点大于它们的不同点,那就更简单了,我们可以把相同的部分提取出去,做成一个 辅助函数。 比如,如果有个函数是这样: 1void function() { 2 3 a(); 4 b() 5 c(); 6 7 if (getOS().equals("MacOS")) { 8 d(); 9 } else { 10 e(); 11 } 12} 其中函数 a(),b(),c()都是一样的,只有函数 d()、e()根据系统有所不同。那么你可以把函数 a(),b(),c()提取出去,代码如下: 1void preFun() { 2 a(); 3 b() 4 c(); 5} 然后分别写两个函数: 1void funMacOS() { 2 preFun(); 3 d(); 4} 和 1void funOther() { 2 preFun(); 3 e(); 4} 这样更改之后,是不是清晰了很多,我们既共享了代码,避免冗余,又做到了每个函数只做一件简单的事情。这样的代码,逻辑就更加清晰了。 工具函数 是的,我再说一遍,每个函数只做一件简单的事情。但是有些时候在我们的工程代码中,我们可能会发现其实里面有很多的重复。 所以一些常用的代码,不管它有多短或者多么的简单,都可以提取出去做成函数,例如有些帮助函数也许就只有2行,但是我们把它封装成一个函数的话,就能大大简化主要函数里面的逻辑。 也许你可能会说,这些函数调用会增加代码开销,但随着硬件发展以及技术变革,这已经是一种过时的观念了。 现代的很多编译器都能自动的把小的函数 内联(inline)到调用它的地方,所以根本不会产生函数调用,也就不会产生任何多余的开销。 那么我可以使用宏来代替工具函数?一行代码搞定了,比如 1#define FillAndSendTxOptions( TRANSSEQ, ADDR, ID, LEN, TxO ) { \ 2afStatus_t stat;                                    \ 3ZDP_TxOptions = (TxO);                              \ 4stat = fillAndSend( (TRANSSEQ), (ADDR), (ID), (LEN) );          \ 5ZDP_TxOptions = AF_TX_OPTIONS_NONE;                 \ 6return stat;                                        \ 7} 同样,这也许也过时了,我不想让宏(macro)来背这个锅,在早期的C语言编译器里,只有宏是静态内联的,所以使用宏是为了达到内联的目的。 然而能否内联,其实并不是宏与函数的根本区别,这里我不细说了,只要记住: 应该尽量避免使用宏。如果想了解可以参考 避免这7个误区,才能让【宏】削铁如泥 为了内联而使用宏,其实是滥用了宏,这会引起各种各样的麻烦,比如使程序难以理解,难以调试,容易出错等等。 尽量使用局部变量和参数 我们应该尽量避免使用全局变量和类成员(class member)来传递信息,举个例子: 1class A { 2 String x; 3 4 void findX() { 5 ... 6 x = ...; 7 } 8 9 void fun() { 10 findX(); 11 ... 12 print(x); 13 } 14} 首先,使用函数findX(),把一个值写入成员x。然后,调用x的值。这样,x就变成了findX和print之间的数据通道。 由于 x属于class A,这样程序就失去了模块化的结构,与我们所说的模块化意义不符了。两个函数依赖于成员x,就不再有明确的输入和输出,而是依赖全局的数据。 函数findX和fun不再能够离开 class A而存在,具有依赖性,并且由于类成员还有可能被其他代码改变,这样就会导致代码变得复杂难以理解,函数的正确性也难以保证。 如果使用局部变量和参数来传递信息,那么这两个函数就不需要依赖于某一个class,不易出错,代码如下: 1String findX() { 2 ... 3 x = ...; 4 return x; 5 } 6 void foo() { 7 String x = findX(); 8 print(x); 9 } 总结 模块化是指解决一个复杂问题时,自顶向下,逐层把系统划分成若干模块的过程,深入理解模块化,什么是真正的模块化,那么我们才能够事半功倍。 如果你喜欢我的文章表达的思维,转发分享是对我的厚爱,期待您的点赞在看,下一期,再见! 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    玩转嵌入式 代码 模块化

  • 还在敲代码? 来看看如何自动生成

    素材来源:最后一个bug 状态(State) 指的是对象在其生命周期中的一种状况,处于某个特定状态中的对象必然会满足某些条件、执行某些动作或者是等待某些事件。 事件(Event) 指的是在时间和空间上占有一定位置,并且对状态机来讲是有意义的那些事情。事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态。 转换(Transition) 指的是两个状态之间的一种关系,表明对象将在第一个状态中执行一定的动作,并将在某个事件发生- 同时某个特定条件满足时进入第二个状态。 动作(Action) 指的是状态机中可以执行的那些原子操作,所谓原子操作指的是它们在运行的过程中不能被其他消息所中断,必须一直执行下去。 二、手工编写状态机 与其他常用的设计模式有所不同,程序员想要在自己的软件系统中加入状态机时,必须再额外编写一部分用于逻辑控制的代码,如果系统足够复杂的话,这部分代码实现和维护起来还是相当困难的。在实现有限状态机时,使用switch语句是最简单也是最直接的一种方式,其基本思路是为状态机中的每一种状态都设置一个case分支,专门用于对该状态进行控制。下面的代码示范了如何运用switch语句,来实现图1中所示的状态机: switch (state) { // 处理状态Opened的分支 case (Opened): { // 执行动作Open open(); // 检查是否有CloseDoor事件 if (closeDoor()) { // 当前状态转换为Closed changeState(Closed) } break; } // 处理状态Closed的分支 case (Closed): { // 执行动作Close close(); // 检查是否有OpenDoor事件 if (openDoor()) { // 当前状态转换为Opened changeState(Opened); } // 检查是否有LockDoor事件 if (lockDoor()) { // 当前状态转换为Locked changeState(Locked); } break; } // 处理状态Locked的分支 case (Locked): { // 执行动作Lock lock(); // 检查是否有UnlockDoor事件 if (unlockDoor()) { // 当前状态转换为Unlocked changeState(Unlocked); } break; } // 处理状态Unlocked的分支 case (Unlocked): { // 执行动作Unlock unlock(); // 检查是否有LockDoor事件 if (lockDoor()) { // 当前状态转换为Locked changeState(Locked) } // 检查是否有OpenDoor事件 if (openDoor()) { // 当前状态转换为Opened changeSate(Opened); } break; } } 使用switch语句实现的有限状态机的确能够很好地工作,但代码的可读性并不十分理想,主要原因是在实现状态之间的转换时,检查转换条件和进行状态转换都是混杂在当前状态中来完成的。例如,当城门处于Opened状态时,需要在相应的case中调用closeDoor()函数来检查是否有必要进行状态转换,如果是的话则还需要调用changeState()函数将当前状态切换到Closed。显然,如果在每种状态下都需要分别检查多个不同的转换条件,并且需要根据检查结果让状态机切换到不同的状态,那么这样的代码将是枯燥而难懂的。从代码重构的角度来讲,此时更好的做法是引入checkStateChange()和performStateChange()两个函数,专门用来对转换条件进行检查,以及激活转换时所需要执行的各种动作。这样一来,程序结构将变得更加清晰: switch (state)  { // 处理状态Opened的分支 case (Opened): { // 执行动作Open open(); // 检查是否有激发状态转换的事件产生 if (checkStateChange()) { // 对状态机的状态进行转换 performStateChange();     } break;   } // 处理状态Closed的分支 case (Closed): { // 执行动作Close close(); // 检查是否有激发状态转换的事件产生 if (checkStateChange()) { // 对状态机的状态进行转换 performStateChange();     } break;   } // 处理状态Locked的分支 case (Locked): { // 执行动作Lock lock(); // 检查是否有激发状态转换的事件产生 if (checkStateChange()) { // 对状态机的状态进行转换 performStateChange();     } break;   } // 处理状态Unlocked的分支 case (Unlocked): { // 执行动作Lock unlock(); // 检查是否有激发状态转换的事件产生 if (checkStateChange()) { // 对状态机的状态进行转换 performStateChange();     } break;   } } 但checkStateChange()和performStateChange()这两个函数本身依然会在面对很复杂的状态机时,内部逻辑变得异常臃肿,甚至可能是难以实现。 在很长一段时期内,使用switch语句一直是实现有限状态机的唯一方法,甚至像编译器这样复杂的软件系统,大部分也都直接采用这种实现方式。但之后随着状态机应用的逐渐深入,构造出来的状态机越来越复杂,这种方法也开始面临各种严峻的考验,其中最令人头痛的是如果状态机中的状态非常多,或者状态之间的转换关系异常复杂,那么简单地使用switch语句构造出来的状态机将是不可维护的。 三、自动生成状态机 为实用的软件系统编写状态机并不是一件十分轻松的事情,特别是当状态机本身比较复杂的时候尤其如此,许多有过类似经历的程序员往往将其形容为"毫无创意"的过程,因为他们需要将大量的时间与精力倾注在如何管理好状态机中的各种状态上,而不是程序本身的运行逻辑。作为一种通用的软件设计模式,各种软件系统的状态机之间肯定会或多或少地存在着一些共性,因此人们开始尝试开发一些工具来自动生成有限状态机的框架代码,而在Linux下就有一个挺不错的选择──FSME(Finite State Machine Editor)。 FSME是一个基于Qt的有限状态机工具,它能够让用户通过图形化的方式来对程序中所需要的状态机进行建模,并且还能够自动生成用C++或者Python实现的状态机框架代码。下面就以城门的状态机为例,来介绍如何利用FSME来自动生成程序中所需要的状态机代码。 3.1状态机建模 首先运行fsme命令来启动状态机编辑器,然后单击工具栏上的"New"按钮来创建一个新的状态机。FSME中用于构建状态机的基本元素一共有五种:事件(Event)、输入(Input)、输出(Output)、状态(State)和转换(Transition),在界面左边的树形列表中可以找到其中的四种。 转换建模 状态转换是整个建模过程中最重要的一个部分,它用来定义有限状态机中的一个状态是如何切换到另一个状态的。例如,当用来控制城门的状态机处于Opened状态时,如果此时有Close事件产生,那么状态机的当前状态将切换到Closed状态,这样一个完整的过程在状态机模型中可以用closeDoor这样一个转换来进行描述。 要在FSME中添加这样一个转换,首先需要在界面左边的树形列表中选择"States"下的"Opened"项,然后按下键盘上的Insert键来添加一个新的转换,接着在右下角的"Name"文本框中输入转换的名字"closeDoor",在"Condition"文本框中输入"Close"表明触发该转换的条件是事件Close的产生,在"Target"下拉框中选择"Closed"项表明该转换发生后状态机将被切换到Closed状态,最后再单击"Apply"按钮,一个新的状态转换关系就定义好了。用同样的办法可以添加状态机所需要的所有转换。 3.2 生成状态机框架 使用FSME不仅能够进行可视化的状态机建模,更重要的是它还可以根据得到的模型自动生成用C++或者Python实现的状态机框架。首先在FSME界面左边的树形列表中选择"Root"项,然后在右下角的"Name"文本框中输入状态机的名字"DoorFSM",再从"Initial State"下拉列表中选择状态"Opened"作为状态机的初始化状态。 在将状态机模型保存为door.fsm文件之后,使用下面的命令可以生成包含有状态机定义的头文件: [xiaowp@linuxgam code]$ fsmc door.fsm -d -o DoorFSM.h 进一步还可以生成包含有状态机实现的框架代码: [xiaowp@linuxgam code]$ fsmc door.fsm -d -impl DoorFSM.h -o DoorFSM.cpp 如果想对生成的状态机进行验证,只需要再手工编写一段用于测试的代码就可以了: /*  * TestFSM.cpp  * 测试生成的状态机框架  */ #include "DoorFSM.h" int main() {   DoorFSM door;   door.A(DoorFSM::Close);   door.A(DoorFSM::Lock);   door.A(DoorFSM::Unlock);   door.A(DoorFSM::Open); } 有限状态机是由事件来进行驱动的,在FSME生成的状态机框架代码中,方法A()可以被用来向状态机发送相应的事件,从而提供状态机正常运转所需要的"动力"。状态机负责在其内部维护一个事件队列,所有到达的事件都会先被放到事件队列中进行等候,从而能够保证它们将按照到达的先后顺序被依次处理。在处理每一个到达的事件时,状态机都会根据自己当前所处的状态,检查与该状态对应的转换条件是否已经被满足,如果满足的话则激活相应的状态转换过程。 使用下面的命令能够将生成的状态机框架和测试代码编译成一个可执行文件: [xiaowp@linuxgam code]$ g++ DoorFSM.cpp TestFSM.cpp -o fsm 由于之前在用fsmc命令生成状态机代码时使用了-d选项,生成的状态机框架中会包含一定的调试信息,包括状态机中每次状态转换时的激活事件、转换前的状态、所经历的转换、转换后的状态等,如下所示: [xiaowp@linuxgam code]$ ./fsm DoorFSM:event:'Close' DoorFSM:state:'Opened' DoorFSM:transition:'closeDoor' DoorFSM:new state:'Closed' DoorFSM:event:'Lock' DoorFSM:state:'Closed' DoorFSM:transition:'lockDoor' DoorFSM:new state:'Locked' DoorFSM:event:'Unlock' DoorFSM:state:'Locked' DoorFSM:transition:'unlockDoor' DoorFSM:new state:'Unlocked' DoorFSM:event:'Open' DoorFSM:state:'Unlocked' DoorFSM:transition:'openDoor' DoorFSM:new state:'Opened' 3.3 定制状态机 目前得到的状态机已经能够响应来自外部的各种事件,并适当地调整自己当前所处的状态,也就是说已经实现了状态机引擎的功能,接下来要做的就是根据应用的具体需求来进行定制,为状态机加入与软件系统本身相关的那些处理逻辑。在FSME中,与具体应用相关的操作称为输出(Output),它们实际上就是一些需要用户给出具体实现的虚函数,自动生成的状态机引擎负责在进入或者退出某个状态时调用它们。 仍然以控制城门的那个状态机为例,假设我们希望在进入每个状态时都添加一部分处理逻辑。首在FSME界面左边的树形列表选择"Outputs"项,然后按下键盘上的Insert键来添加一个新的输出,接着在右下方的"Name"文本框中输入相应的名称,再单击"Apply"按钮,一个新的输出就创建好了,如图7所示。用同样的办法可以添加状态机所需要的所有输出。 图7 添加输出 当所有的输出都定义好之后,接下来就可以为状态机中的每个状态绑定相应的输出。首先在FSME界面左侧的"States"项中选择相应的状态,然后从右下角的"Available"列表框中选择与该状态对应的输出,再单击"<"按钮将其添加到"In"列表中,如图8所示。用同样的办法可以为状态机中的所有状态设置相应的输出,同一个状态可以对应有多个输出,其中In列表中的输出会在进入该状态时被调用,而Out列表中的输出则会在退出该状态时被调用,输出调用的顺序是与其在In或者Out列表中的顺序相一致的。 图8 为状态设置输出 由于对状态机模型进行了修改,我们需要再次生成状态机的框架代码,不过这次不需要加上-d参数: [xiaowp@linuxgam code]$ fsmc door.fsm -o DoorFSM.h [xiaowp@linuxgam code]$ fsmc door.fsm -d -impl DoorFSM.h -o DoorFSM.cpp 我们在新的状态机模型中添加了enterOpend、enterClosed、enterLocked和enterUnlocked四个输出,因此生成的类DoorFSM中会包含如下几个纯虚函数: virtual void enterOpened() = 0; virtual void enterLocked() = 0; virtual void enterUnlocked() = 0; virtual void enterClosed() = 0; 显然,此时生成的状态机框架不能够再被直接编译了,我们必须从类DoorFSM派生出一个子类,并提供对这几个纯虚函数的具体实现: /*  * DoorFSMLogic.h  * 状态机控制逻辑的头文件  */ #include "DoorFSM.h" class DoorFSMLogic : public DoorFSM { protected: virtual void enterOpened(); virtual void enterLocked(); virtual void enterUnlocked(); virtual void enterClosed(); }; 正如前面所提到过的,这几个函数实际上代表的正是应用系统的处理逻辑,作为例子我们只是简单地输出一些提示信息: /*  * DoorFSMLogic.cpp  * 状态机控制逻辑的实现文件  */ #include "DoorFSMLogic.h" #include void DoorFSMLogic::enterOpened() { std::cout << "Enter Opened state." << std::endl; } void DoorFSMLogic::enterClosed() { std::cout << "Enter Closed state." << std::endl; } void DoorFSMLogic::enterLocked() { std::cout << "Enter Locked state." << std::endl; } void DoorFSMLogic::enterUnlocked() { std::cout << "Enter Unlocked state." << std::endl; } 同样,为了对生成的状态机进行验证,我们还需要手工编写一段测试代码: /*  * TestFSM.cpp  * 测试状态机逻辑  */ #include "DoorFSMLogic.h" int main() {   DoorFSMLogic door;   door.A(DoorFSM::Close);   door.A(DoorFSM::Lock);   door.A(DoorFSM::Unlock);   door.A(DoorFSM::Open); } 使用下面的命令能够将生成的状态机框架和测试代码编译成一个可执行文件: [xiaowp@linuxgam code]$ g++ DoorFSM.cpp DoorFSMLogic.cpp TestLogic.cpp -o logic 运行结果如下所示: [xiaowp@linuxgam code]$ ./logic Enter Closed state. Enter Locked state. Enter Unlocked state. Enter Opened state. 四、小结 在面向对象的软件系统中,有些对象具有非常复杂的生命周期模型,使用有限状态机是描述这类对象最好的方法。作为一种软件设计模式,有限状态机的概念虽然不算复杂,实现起来也并不困难,但它的问题是当状态机的模型复杂到一定的程度之后,会带来实现和维护上的困难。Linux下的FSME是一个可视化的有限状态机建模工具,而且支持状态机框架代码的自动生成,借助它可以更加轻松地构建基于有限状态机的应用系统。 参考资料 从Wiki百科全书 http://en.wikipedia.org/wiki/Finite_state_automaton开始,你可以了解到许多同状态机相关的计算理论知识。 状态机是UML的一个重要组成部分,Robert C. Martin在他的文章UML Tutorial: Finite State Machines中,介绍了如何使用UML语言来对状态机进行建模,你可以通过网址 http://www.objectmentor.com/resources/articles/umlfsm.pdf可以找到这一文档。 FSME是Linux下一个基于Qt的状态机建模工具,它能够自动生成状态机框架代码,并且同时支持C++和Python语言,通过网站 http://fsme.sourceforge.net/你可以了解到有关FSME的更多信息,并能够下载最新版本的FSME。 Qfsm也是一个运行在Linux下的状态机建模工具,它不仅提供了可视化的状态机编辑器,而且还能够对生成的状态机进行实时模拟,通过网站 http://qfsm.sourceforge.net/可以了解到Qfsm的更多信息。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    玩转嵌入式 Linux 代码

  • 关于堆栈的讲解(我见过的最经典的)

    关于堆栈的讲解(我见过的最经典的)

    一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束后有系统释放4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放5、程序代码区—存放函数体的二进制代码。 二、例子程序 这是一个前辈写的,非常详细 //main.cppint a = 0; //全局初始化区int a = 0; //全局初始化区char *p1; //全局未初始化区main() {    int b; //栈    char s[] = "abc"; //栈    char *p2; //栈    char *p3 = "123456"; //123456\0在常量区,p3在栈上。    static int c = 0; //全局(静态)初始化区    p1 = (char *)malloc(10);    p2 = (char *)malloc(20);    //分配得来得10和20字节的区域就在堆区。    strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。} 二、堆和栈的理论知识 2.1申请方式 stack:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间heap:需要程序员自己申请,并指明大小,在c中malloc函数如p1 = (char *)malloc(10);在C++中用new运算符如p2 = (char *)malloc(10);但是注意p1、p2本身是在栈中的。 2.2 申请后系统的响应 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 2.3 申请大小的限制 栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 2.4 申请效率的比较: 栈由系统自动分配,速度较快。但程序员是无法控制的。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。 2.5 堆和栈中的存储内容 栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。 2.6 存取效率的比较 char s1[] = "aaaaaaaaaaaaaaa";char *s2 = "bbbbbbbbbbbbbbbbb";aaaaaaaaaaa是在运行时刻赋值的;而bbbbbbbbbbb是在编译时就确定的;但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。比如: #includevoid main() {    char a = 1;    char c[] = "1234567890";    char *p ="1234567890";    a = c[1];    a = p[1];    return;} 对应的汇编代码 10: a = c[1];00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]0040106A 88 4D FC mov byte ptr [ebp-4],cl11: a = p[1];0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]00401070 8A 42 01 mov al,byte ptr [edx+1]00401073 88 45 FC mov byte ptr [ebp-4],al 第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。 2.7小结: 堆和栈的区别可以用如下的比喻来看出:使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。 三 、windows进程中的内存结构 在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识。 接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。 首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码: #include int g1=0, g2=0, g3=0;int main(){    static int s1=0, s2=0, s3=0;    int v1=0, v2=0, v3=0;    //打印出各个变量的内存地址        printf("0x%08x\n",&v1); //打印各本地变量的内存地址    printf("0x%08x\n",&v2);    printf("0x%08x\n\n",&v3);    printf("0x%08x\n",&g1); //打印各全局变量的内存地址    printf("0x%08x\n",&g2);    printf("0x%08x\n\n",&g3);    printf("0x%08x\n",&s1); //打印各静态变量的内存地址    printf("0x%08x\n",&s2);    printf("0x%08x\n\n",&s3);    return 0;} 编译后的执行结果是: 0x0012ff780x0012ff7c0x0012ff800x004068d00x004068d40x004068d80x004068dc0x004068e00x004068e4 输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。 ├———————┤低端内存区域│ …… │├———————┤│ 动态数据区 │├———————┤│ …… │├———————┤│ 代码区 │├———————┤│ 静态数据区 │├———————┤│ …… │├———————┤高端内存区域 堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程,以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。两者通过“__stdcall”和“__cdecl”前缀区分。先看下面这段代码: #include void __stdcall func(int param1,int param2,int param3){    int var1=param1;    int var2=param2;    int var3=param3;    printf("0x%08x\n",param1); //打印出各个变量的内存地址    printf("0x%08x\n",param2);    printf("0x%08x\n\n",param3);    printf("0x%08x\n",&var1);    printf("0x%08x\n",&var2);    printf("0x%08x\n\n",&var3);    return;}int main() {    func(1,2,3);    return 0;} 编译后的执行结果是: 0x0012ff780x0012ff7c0x0012ff800x0012ff680x0012ff6c0x0012ff70 ├———————┤

    嵌入式ARM 堆栈 C语言 C

  • 简单工厂方法模式_C语言实现

    嵌入式大杂烩 C语言 工厂方法 简单工厂

  • 机器学习基础图表:概念、原理、历史、趋势和算法

    Part 1 机器学习概览 什么是机器学习? 机器通过分析大量数据来进行学习。比如说,不需要通过编程来识别猫或人脸,它们可以通过使用图片来进行训练,从而归纳和识别特定的目标。 机器学习和人工智能的关系 机器学习是一种重在寻找数据中的模式并使用这些模式来做出预测的研究和算法的门类。机器学习是人工智能领域的一部分,并且和知识发现与数据挖掘有所交集。 机器学习的工作方式 选择数据:将你的数据分成三组:训练数据、验证数据和测试数据 模型数据: 使用训练数据来构建使用相关特征的模型 验证模型: 使用你的验证数据接入你的模型 测试模型: 使用你的测试数据检查被验证的模型的表现 使用模型: 使用完全训练好的模型在新数据上做预测 调优模型: 使用更多数据、不同的特征或调整过的参数来提升算法的性能表现 机器学习所处的位置 传统编程:软件工程师编写程序来解决问题。首先存在一些数据→为了解决一个问题,软件工程师编写一个流程来告诉机器应该怎样做→计算机遵照这一流程执行,然后得出结果 统计学:分析师比较变量之间的关系 机器学习:数据科学家使用训练数据集来教计算机应该怎么做,然后系统执行该任务。首先存在大数据→机器会学习使用训练数据集来进行分类,调节特定的算法来实现目标分类→该计算机可学习识别数据中的关系、趋势和模式 智能应用:智能应用使用人工智能所得到的结果,如图是一个精准农业的应用案例示意,该应用基于无人机所收集到的数据 机器学习的实际应用 机器学习有很多应用场景,这里给出了一些示例,你会怎么使用它? 快速三维地图测绘和建模:要建造一架铁路桥,PwC的数据科学家和领域专家将机器学习应用到了无人机收集到的数据上。这种组合实现了工作成功中的精准监控和快速反馈。 增强分析以降低风险:为了检测内部交易,PwC将机器学习和其它分析技术结合了起来,从而开发了更为全面的用户概况,并且获得了对复杂可疑行为的更深度了解。 预测表现最佳的目标:PwC使用机器学习和其它分析方法来评估 Melbourne Cup 赛场上不同赛马的潜力。 Part 2 机器学习的演化 几十年来,人工智能研究者的各个「部落」一直以来都在彼此争夺主导权。现在是这些部落联合起来的时候了吗?他们也可能不得不这样做,因为合作和算法融合是实现真正通用人工智能(AGI)的唯一方式。这里给出了机器学习方法的演化之路以及未来的可能模样。 五大流派 符号主义:使用符号、规则和逻辑来表征知识和进行逻辑推理,最喜欢的算法是:规则和决策树 贝叶斯派:获取发生的可能性来进行概率推理,最喜欢的算法是:朴素贝叶斯或马尔可夫 联结主义:使用概率矩阵和加权神经元来动态地识别和归纳模式,最喜欢的算法是:神经网络 进化主义:生成变化,然后为特定目标获取其中最优的,最喜欢的算法是:遗传算法 Analogizer:根据约束条件来优化函数(尽可能走到更高,但同时不要离开道路),最喜欢的算法是:支持向量机 演化的阶段 1980年代 —— 主导流派:符号主义,架构:服务器或大型机,主导理论:知识工程,基本决策逻辑:决策支持系统,实用性有限 1990年代到2000年 —— 主导流派:贝叶斯,架构:小型服务器集群,主导理论:概率论,分类:可扩展的比较或对比,对许多任务都足够好了 2010年代早期到中期 —— 主导流派:联结主义,架构:大型服务器农场,主导理论:神经科学和概率,识别:更加精准的图像和声音识别、翻译、情绪分析等 流派有望合作融合到一起 2010年代末期 —— 主导流派:联结主义+符号主义,架构:许多云,主导理论:记忆神经网络、大规模集成、基于知识的推理,简单的问答:范围狭窄的、领域特定的知识共享 2020年代+ —— 主导流派:联结主义+符号主义+贝叶斯+……,架构:云计算和雾计算,主导理论:感知的时候有网络,推理和工作的时候有规则,简单感知、推理和行动:有限制的自动化或人机交互 2040年代+ —— 主导流派:算法融合,架构:无处不在的服务器,主导理论:最佳组合的元学习,感知和响应:基于通过多种学习方式获得的知识或经验采取行动或做出回答 Part 3 机器学习的算法 你应该使用哪种机器学习算法?这在很大程度上依赖于可用数据的性质和数量以及每一个特定用例中你的训练目标。不要使用最复杂的算法,除非其结果值得付出昂贵的开销和资源。这里给出了一些最常见的算法,按使用简单程度排序。 决策树 Decision Tree 在进行逐步应答过程中,典型的决策树分析会使用分层变量或决策节点,例如,可将一个给定用户分类成信用可靠或不可靠。 优点: 擅长对人、地点、事物的一系列不同特征、品质、特性进行评估,场景举例: 基于规则的信用评估、赛马结果预测。 支持向量机 Support Vector Machine 基于超平面(hyperplane),支持向量机可以对数据群进行分类。 优点: 支持向量机擅长在变量 X 与其它变量之间进行二元分类操作,无论其关系是否是线性的,场景举例: 新闻分类、手写识别。 回归 Regression 回归可以勾画出因变量与一个或多个因变量之间的状态关系。在这个例子中,将垃圾邮件和非垃圾邮件进行了区分。 优点: 回归可用于识别变量之间的连续关系,即便这个关系不是非常明显, 场景举例: 路面交通流量分析、邮件过滤。 朴素贝叶斯分类 Naive Bayes Classification 朴素贝叶斯分类器用于计算可能条件的分支概率。每个独立的特征都是「朴素」或条件独立的,因此它们不会影响别的对象。例如,在一个装有共 5 个黄色和红色小球的罐子里,连续拿到两个黄色小球的概率是多少?从图中最上方分支可见,前后抓取两个黄色小球的概率为 1/10。朴素贝叶斯分类器可以计算多个特征的联合条件概率。 优点:对于在小数据集上有显著特征的相关对象,朴素贝叶斯方法可对其进行快速分类,场景举例:情感分析、消费者分类。 隐马尔可夫模型 Hidden Markov model 隐马尔可夫过程是完全确定性的 —— 一个给定的状态经常会伴随另一个状态。交通信号灯就是一个例子。相反,隐马尔可夫模型通过分析可见数据来计算隐藏状态的发生。随后,借助隐藏状态分析,隐马尔可夫模型可以估计可能的未来观察模式。在本例中,高或低气压的概率(这是隐藏状态)可用于预测晴天、雨天、多云天的概率。 优点:容许数据的变化性,适用于识别(recognition)和预测操作,场景举例:面部表情分析、气象预测。 随机森林 Random forest 随机森林算法通过使用多个带有随机选取的数据子集的树(tree)改善了决策树的精确性。本例在基因表达层面上考察了大量与乳腺癌复发相关的基因,并计算出复发风险。 优点: 随机森林方法被证明对大规模数据集和存在大量且有时不相关特征的项(item)来说很有用,场景举例: 用户流失分析、风险评估。 循环神经网络 Recurrent neural network 在任意神经网络中,每个神经元都通过 1 个或多个隐藏层来将很多输入转换成单个输出。循环神经网络(RNN)会将值进一步逐层传递,让逐层学习成为可能。换句话说,RNN 存在某种形式的记忆,允许先前的输出去影响后面的输入。 优点:循环神经网络在存在大量有序信息时具有预测能力,场景举例:图像分类与字幕添加、政治情感分析。 长短期记忆与门控循环单元神经网络 LSTM & GRU nerual network 早期的 RNN 形式是会存在损耗的。尽管这些早期循环神经网络只允许留存少量的早期信息,新近的长短期记忆(LSTM)与门控循环单元(GRU)神经网络都有长期与短期的记忆。换句话说,这些新近的 RNN 拥有更好的控制记忆的能力,允许保留早先的值或是当有必要处理很多系列步骤时重置这些值,这避免了「梯度衰减」或逐层传递的值的最终 degradation。LSTM 与 GRU 网络使得我们可以使用被称为「门(gate)」的记忆模块或结构来控制记忆,这种门可以在合适的时候传递或重置值。 优点:长短期记忆和门控循环单元神经网络具备与其它循环神经网络一样的优点,但因为它们有更好的记忆能力,所以更常被使用,场景举例:自然语言处理、翻译。 卷积神经网络 convolutional neural network 卷积是指来自后续层的权重的融合,可用于标记输出层。 优点: 当存在非常大型的数据集、大量特征和复杂的分类任务时,卷积神经网络是非常有用的,场景举例: 图像识别、文本转语音、药物发现。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    嵌入式ARM 人工智能 机器学习

  • MISRA C:2012 又是什么标准?

    编排 | strongerHuang 微信公众号 | 嵌入式专栏 C语言的标准有很多,之前给大家分享过相关的内容,比如:C89、C99标准,ANSI C、ISO C、Standard C标准等。 可能你在一些地方还看到过:MISRA C:2012,MISRA C++:2008,那你知道这是什么吗? 今天分享的就是另外一种C语言标准:MISRA C. 嵌入式专栏 1 MISRA C MISRA C是由汽车产业软件可靠性协会(MISRA)提出的C语言开发标准。 其目的是在增进嵌入式系统的安全性及可移植性,针对C++语言也有对应的标准MISRA C++。 MISRA C一开始主要是针对汽车产业,不过其他产业也逐渐开始使用MISRA C:包括航天、电信、国防、医疗设备、铁路等领域中都已有厂商使用MISRA C。 MISRA C的第一版《Guidelines for the use of the C language in vehicle based software》是在1998年发行,一般称为MISRA-C:1998.。MISRA-C:1998有127项规则,规则从1号编号到127号,其中有93项是强制要求,其余的34项是推荐使用的规则。 在2004年时发行了第二版的MISRA C的第一版《Guidelines for the use of the C language in critical systems》(或称作MISRA-C:2004),其中有许多重要建议事项的变更,其规则也重新编号。MISRA-C:2004有141项规则,其中121项是强制要求,其余的20项是推荐使用的规则。规则分为21类,从“开发环境”到“运行期错误”。 2012年发布第三版,为当前最新有效的C语言规范版本,称为MISRA C:2012。 MISRA C 版本历史 MISRA版本 发布年份 C语言版本 指令 数量 规则 数量 指南 总数 1998 1998 C90 不详 127 不详 2004 2004 C90 不详 142 不详 2012 2012 C99 16 143 159 2012 AMD-1 2016 C99 17 156 173 2012 AMD-2 2020 C11 17 158 175 嵌入式专栏 2 MISRA C:2012(修订版2) 当前最新有效的C语言规范版本为MISRA C:2012,下面来讲讲其修订版2。 MISRA C工作组发布了对MISRA C:2012的修订版,以支持称为“C11”的C标准,并正式批准为ISO/IEC 9899:2011。C11已得到广泛使用,对于一直推迟迁移到C11的项目和组织,这是一个广受欢迎的公告。C11还取代了C99(标准ISO/IEC 9899:1999),并已被C18(标准ISO/IEC 9899:2018)取代。除了更新的C语言准则之外,MISRA C工作组还发布了MISRA 2020合规性指南。 MISRA C:2012(修订版2)现在引用ISO/IEC 9899:2011,并包含C语言更新,为可能使用但受限制的功能和受禁止的功能提供指导,除非其存在您的团队软件审查过程已批准的偏差。在制定修订版2的过程中,还获得了利用先前的补充内容纠正任何已知问题的机会。MISRA工作组的任务不止是提供指导,以防止发生不可预测的行为,减少或消除编码缺陷并在嵌入式软件系统的环境中促进代码安全、安全性、可移植性和可靠性。 新的MISRA C:2012标准: 添加了适用于C11中新功能的新MISRA规则的一个示例,即规则1.4,“不得使用紧急语言功能。” 如果使用设施和_Thread_local存储类说明符,则可能是这种情况的一个实例。该规则将使用违反类别类型“必需”标记C11语言结构。 C11标准化了可能在多核平台上运行的多线程程序的语义,以及使用原子变量的轻量级线程间通信。使用线程本地的全局内存,其中已标识出未定义和未指定行为的实例,包括未满足预期的已定义行为。 向前迈进并符合MISRA C:2012(修订版2),如果我使用_Thread_local,不仅需要进行偏离,而且还需要采取保证措施来解决危害安全性的行为。 _Generic关键字是另一个不应使用的C11语言新功能,它可能表现出不良行为,并且一些人发现C11标准在某些情况下含糊不清。_Generic运算符是一种宏重载。它用于帮助程序员将任何宏都用作通用宏,以使其更高效。下面的代码行显示_Generic关键字如何用于声明不同类型的数据类型的任何宏,以及如何将其声明为不同方法的泛型。以下面的VOL宏示例为例;VOL(x)根据x的类型转换为VOLc(x),VOLl(x),VOL(x)或VOLf(x)。 #define VOL(x) _Generic((x), char: VOLc, long double: VOLl, default: VOL, float: VOLf)(x) 安全编码 安全漏洞的一个常见原因是使用了中定义的标准库函数系统。MISRA C:2012(修订版2)添加了新的规则21.21,该规则规定不得使用标准库函数系统。系统调用是一个阻塞函数,用于执行子进程和命令,等待子进程终止并返回其退出值。认识到原型为“int系统(const char *command);”不需要是单个命令,而可以是一个管道或一系列管道。(例如,系统("pngtopnm \"My Picture.png\" | pnmtoxwd > fout.xwd && xwud fout.xwd");)由于可变命令是由用户提供的数据构成的,因此攻击者可能会发现 引用并在父级上下文中执行任意命令。一些建议的措施可能是利用预定的命令字符串或一起绕开系统调用,而使用spawn代替。 合规报告 对于声称符合MISRA的要求,有一些书面指南,这些指南在过去的几年中不断完善和修订。本文档的最新版本是MISRA Compliance 2020,它于2月发布。从较高的角度来看,适当报告了使用良好的软件开发过程的使用情况,准确地应用了哪些指南以及执行方法的有效性的列表,包括偏差的程度或程度,以及为了声称MISRA符合要求,必须考虑到项目外开发的所有软件组件的状态。Parasoft DTP提供了专用的报告扩展,完全符合MISRA Compliance标准的要求。DTP将指导您完成构建准则执行计划(GEP)和准则重新分类计划(GRP)的过程,并自动生成准则合规摘要(GCS)以及已批准偏差的完整列表。自动化报告消除了繁琐的手工工作,使组织能够遵循MISRA编码准则现在强制执行的合规性流程。 最后 还有其他新的MISRA C:2012(修订版2)规则,例如_Noreturn函数说明符、_Atomic类型说明符、_Alignas对齐说明符和_Alignof运算符,非常引人注目。使用这些类型说明符将触发类别“必需的违规”,并且将不被使用,从而解决了C11覆盖和安全漏洞的问题。此外,还进行了许多修订版2更新和文本替换,以澄清和改进标准。同样,再次提醒您非常重要的一点是,与该标准一起,用户现在可以遵循MISRA Compliance 2020指南的强制性和补充性法规遵循版本。感谢MISRA工作组继续做出色的工作,并为软件界做出了巨大贡献。

    strongerHuang C语言 安全编码

  • 手动拆解示波器,了解机内高科技

    编排 | strongerHuang 微信公众号 | 嵌入式专栏 相信大家都知道示波器,也都用过示波器,但大多数人都没有拆过吧? 拆解世博之前,我们先了解一下示波器的进化简史。 嵌入式专栏 1 示波器进化简史 纯模拟机器,使用示波管显示X-Y扫描成像显示波形,到后期有字符叠加功能可以实现简单的测量参数显示,巅峰之作为泰克7000系列。 数字机以AD转换器加DSP或者FPGA为控制器对模拟信号进行采样处理显示缓存。 第三代示波器——数字荧光示波器(DPO-Digital Phosphor Oscilloscope) 以数字荧光示波器为基础,在模拟通道的基础上加入逻辑分析意义数字通道,并将触发通道扩展至数字通道使数字通道与模拟通道可同时测量显示。 代表作为安捷伦54622D: 第五代示波器也是今天的主角——混合域示波器(MDO Mixed Domain Oscilloscope)

    strongerHuang 示波器 模拟示波器

  • 单片机数据通信怎么学?这个工具要用好:串口通信

    单片机数据通信怎么学?这个工具要用好:串口通信

    刚开始学单片机的你,是不是会因用程序把LED点亮而感到高兴,会因用程序把数码管点亮而感到高兴。这是好事,这也是想继续学习下去的动力。 但是到了与数据相关的实验时,却感觉很难有所进步。有时候,把驱动写好了,下载到单片机后,一点反应都没有,可是又不知道问题出在哪里,数据通信又不像LED那样可以用万用表测出到底有没有电。 这是学习单片机和STM32的一道坎。又或者说,这是一条河,阻拦着你的去路的河,有一条河你会怎么办?过去的方法很多,但是笔者觉得较快的方法就是借助原有工具渡过去。过去之后你会发现河的那边是一个不一样的世界。 那这个原有的工具是什么呢?那就是"串口通信"。 串口通信介绍 串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式......这种太过理论了,看似懂了,但又不懂。还是用我笔者自己的话来说吧。 串口通信就是可以把程序在单片机或者STM32芯片中运行的结果发送到电脑的一种通信方式。如何使用串口通讯,你需要知道的几个重要的知识点:1)串口通信使用到的GPIO引脚配置 GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能USART1,GPIOA时钟 //USART1_TX GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART1_RX GPIOA.10初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 串口使用的的GPIO口是PA9和PA10,所以只需配置这两个IO口的输入输出模式就可以了。 2)串口主要参数设置(直接看程序) USART_InitTypeDef USART_InitStructure;//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据格式,8位USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式USART_Init(USART1, &USART_InitStructure); //初始化串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断 串口参数配置无法就是配置串口的波特率、数据格式、停止位、奇偶校验、硬件流、收发模式。除了波特率需要改变其他的参数都不需要管。直接复制拿来用。 3)串口中断配置 NVIC_InitTypeDef NVIC_InitStructure;//Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;//子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能NVIC_Init(&NVIC_InitStructure);//根据指定的参数初始化VIC寄存器 4)串口使能 USART_Cmd(USART1, ENABLE); //使能串口1 5)编写串口中断处理函数 END

    嵌入式ARM 数据通信 单片机 串口通信

  • 那些在一个公司死磕了5-10年的人,最后都怎么样了?

    那些在一个公司死磕了5-10年的人,最后都怎么样了?

    最近在知乎上看到一个话题 那些在一个公司死磕了5-10年的人最后都怎么样了?" 在互联网红利爆发,人心躁动的今天,可以在一个公司磕到5~10年,真的很不容易,我记得前东家要是有人呆满8年,就可以鲜花伺候,附送一个小金块,据说价值不菲。 下面是知乎作者"沈世钧"的一个回答,感觉还不错,分享给大家。 我是一名老程序员,在这家公司(500强外企),到今天已经服务了10年,目前的职称是“高级软件技术专家”。 好多人,尤其是在软件圈,一听闻我在这家公司已经服务了10年,无不大吃一惊,觉得我要么是技术烂,要么是没有追求。 其实,都不是。 我之所以愿意呆在这家公司,实在是因为她确实很棒,满足我的一切需求。 工作和生活的平衡 我大概回忆了下,在这家公司的10年,加班的时间全部加起来不超过10天。最近3年则一天都没有。 20天年假,病假无算,基本上随请随休。 我从来没听说过那个领导不批员工年假的。 这样温和的工作,且不说在“996”成风的 IT圈,就是在任何传统行业,哪怕是公务员,也不多见。 对年轻人来说,高强度加班可能不是什么大问题。但人过了30,有了家庭,琐事缠身,精力每况愈下,愈发觉得能拥有这样一份工作,是人生之幸。 顶尖的技术 我所在业务部,主打技术是人工智能。而在某些细分领域,10年以来,公司的算法一直保持业界领先。 而是我虽然是一名软件开发人员,耳濡目染,却也一直接触着业界最前沿技术。 公司鼓励员工大胆尝试新技术,也不限制工程师的发展方向。10年以来,无论是桌面开发(C#/WPF)、还是Java后端开发,还是前端开发,我都较深入的参与过。 所以相对于有些公司,专岗专人,我在技术的广度上,则没有受到明显的限制。 软实力 除了技术“硬实力”,公司还特别注重发展员工的“软实力”,例如演讲能力,文案能力等等。 而这种软实力,越是职业生涯往后发展,就越有价值。 我常想,如果有一天,我失去了当前的工作,也许就再没有人愿意雇用我去写代码。到那时候,也许我可以凭着我这些年的积累,可以去应聘一些其他岗位,例如售前、售后、培训师等。 好歹有口饭吃。 职业发展 我的职业发展,总体走了一条“技术->管理->技术”的路线。 从技术走向管理是因为典型的“学而优则仕”。 从管理再回到技术,则是追随本心。整个过程,公司都尊重我的想法,没有设置任何的障碍。 今年我41岁,每天依旧奋斗在编程第一线。在这家公司,是再正常不过的一件事,没有一丝的尴尬。 舒适的工作环境 我去过很多互联网公司,很多办公环境都是大厅,大长桌。连个最基本的隔断都没有。工位之间,人挤人,恨不得伸个懒腰会碰到别人。 而我们公司人均办公面积20平方米,很多人都同时拥有3台显示器。工作之余,或伸腿躺下,或站起来做几个俯卧撑,那是再轻松不过的事情。 而资历深的老员工,很多还有独立办公室,无论管理还是技术。 有竞争力的薪水 当然,任何一家公司,再好的技术、再舒服的工作环境,如果给太少的钱,无疑都是耍流氓。 在过去的10年中,前8年,我的薪水在同资历的朋友中,一直靠前。但也就这两年,随着互联网的强势发展,朋友中才陆陆续续有人反超了我,但也是个别,而且多的并不夸张。 例如朋友在互联网公司挣1万,那我可能就挣8500。 但是,朋友为了这多出15%,却多付出了将近一倍的时间,而且还是最珍贵的休息时间。这个投入产出比,在我看来,实在太低。 当然,我不是说对所有的人,这个公司都是完美的。但具体到我,在每个时间点上,却可能是最适合我的。 早10年的时候,我年轻,也缺钱,并且精力旺盛,也能承受高强度的加班。但那时我的薪水却高于同资历的人,所以自没有离开的理由。 现在,虽然我的薪水优势已不在,但我却已经40岁了,精力大不如前,需要更多的关注家庭和健康。而现在我如果为了追求不多的加薪,而跳槽到996成风的互联网企业,无疑是准备向死而生。 当然,这世界上永远不会有完美的东西。即使有,也不会在所有的时候,让一个人全占了。 这家公司最大的问题是,这两年外企总体在走下坡路。就在我身边,大规模的裁员就发生过多次。 所以,今天,虽然我每天认真工作,真心的希望这家公司好。但我也有心理准备,如果哪天真裁员到我头上,我也没啥可说的。 我准备领了补偿金(n+3),回家好好调整一段时间,然后再开始人生的下一段旅程。 但无论如何,在心里,我和这家公司都有无比深的感情。她影响了我,改造了我,是我人生最重要,最愉快的的一段时光。我相信,到死的那天,在我临终回忆的时候,我的心里一定有她抹不去的影子。

    嵌入式ARM 互联网 人工智能 知乎

  • 台积电又将涨价,最高涨25%,芯片缺货要到2022年

    编排 | strongerHuang 微信公众号 | 嵌入式专栏 据台媒报道,近日市场传出台积电将调升12吋晶圆代工价,台积电将从今年4月开始调涨价格,每片约涨400美元(约新台币1.14万元),涨幅达25%,且将逐季调涨,将使得台积电整体报价再创历史新高。 台积电涨价传言 对于相关传言,台积电3月28日表示,台积电公司致力于提供客户价值,不评论价格问题。 有IC设计企业则透露,先前多数晶圆代工厂陆续调升价格,台积电报价相对没太大波动,但由于晶圆代工产能持续供不应求,台积电近期也开始调整价格,包括8吋、12吋都有。 半导体业界人士观察,台积电和长期合作的大客户多半已在前一年都已定隔年的价格与合约,今年初已因市场需求强劲,以前传统的打折(约3%至5%)已经取消,相当第一次进行了变相涨价,而这次则是直接涨价。 一名与台积电往来多年的IC设计从业者指出,台积电先进制程技术领先,过往向来有定价优势,先前台积电高层已多次对外强调与客户是长期合作的伙伴关系,不会任意涨价。但现阶段晶圆代工产能确实吃紧,因此台积电也会有相关价格方面的调涨。 业界认为,台积电调高部分客户报价,应多为短期或新客户,比如非主力应用的挖矿相关订单,主要考虑到挖矿商业模式不确定因素众多,又受制各国货币或用电政策,属于短期机会订单,因此台积电自然会限缩和通过价格控管来降低不确定的风险。 台积电去年全球独家量产7nm与5nm制程技术,据研调机构IC Insights估计,台积电每片晶圆营收达1634美元,居同行业之首,比联电高出1.42倍。 IC Insights指出,台积电、联电、格芯(Globalfoundries)与中芯国际4家纯晶圆代工厂中,2020年有3家厂商每片晶圆营收较2019年增加,仅格芯每片晶圆营收减少1%。台积电2020年每片晶圆营收1634美元,较2019年增加6.79%;中芯国际2020年每片晶圆营收增加约2.39%;联电也增加约8.87%。 依据各家晶圆代工厂量产技术蓝图,台积电先进制程量产进度一马当先,其中,5nm量产时间领先三星约六个月,今年内更会开始试产4nm,并规划2022年量产3nm。 IC Insights指出,台积电持续稳居全球最大专业晶圆代工厂,估计每月总产能270万片,占全球总产能13.1%,去年还有多项扩建计划推动,包含台南南科14厂再购入两个基地用于扩充,以及南科18第一与第二期量产外,还有第三至六期扩建计划,同时也在中科15厂开设第十期生产线。 缺芯持续到2022年 虽然包括台积电、中芯国际等晶圆代工厂都在扩产,但半导体产能供不应求的现象还将持续很长一段时间,从而导致芯片缺货、涨价的问题。 据晶圆代工厂力积电董事长黄崇仁表示,半导体产业已经出现结构性问题,产能短缺问题无法解决,因为没有新产能开出,产能供不应求情况延续到明年,芯片缺货可能缺到明年底,而晶圆代工价格自去年底以来涨价幅度已达30~40%,还需要再涨一波才会回到合理价格。 黄崇仁表示,在8吋及12吋成熟制程部份,因为包括台积电、联电等大厂不太可能回头投资成熟制程,扩张的更多是先进制程,缺货问题恐怕无法解决目前所有的产品的晶圆代工产能都很满,不管是面板驱动IC、电源管理IC、存储器、MOSFET等产能全部吃紧。 免责声明:本文素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。 ------------ END ------------

    strongerHuang 台积电 芯片

  • 轻松理解bin、hex、axf和elf文件格式

    在嵌入式软件开发中,bin、hex、axf和elf这四种格式的文件很常见。 之前我分享的STVP、ST-LINK Utility、STM32CubeProg这些下载编程工具的时候,都用到了bin、hex格式的文章。 作为普通嵌入式软件开发者,可能只知道如何使用他们,并不会在意这些文件里面具体是什么内容。 1. 总述 bin 和 hex 大家都不陌生,就是我们下载到芯片的程序文件。 bin文件只是单纯的程序数据,hex除程序数据之外还有一定格式数据。 而 axf 和 bin、 hex 同样也属于程序文件,差别在于 axf 具有更多的调试信息。 用一个表格来区分bin、hex和axf三者的关系: bin hex axf 程序数据 程序数据 程序数据 地址、类型、校验等标记信息 地址、类型、校验等标记信息 调试信息 你会发现,同样一段代码,编译生成的bin文件最小,axf最大。 在嵌入式Linux中还有一种文件 ELF(Executable and Linkable Format,可执行与可链接格式)也算是一种程序文件,这种文件包含信息更多、更复杂。 下面分别来描述bin、hex、axf和elf这四种格式文件。 2. bin文件 bin 是 binary 的缩写,直白的翻译即为二进制文件,在这里理解为可执行的机器代码(程序)文件,因为计算机存储只有 0 和 1。 当然,bin 除了是程序文件的含义,还有其他含义,比如虚拟光驱文件,我们下载的一个 Windows 镜像文件后缀就可能是bin。 bin 相对于hex、axf是一种最简单的程序文件,只有程序数据,程序文件有多大,程序也就多大。 因此,你下载 bin 程序文件的时候,必须要设置起始地址,比如:通过STM32 ST-LINK Utility工具下载bin文件: 而hex则不可修改(文件中包含地址信息): 3. hex文件 hex 格式文件由 Intel 制定的一种十六进制标准文件格式,是由编译器转换而成的一种用于下载到处理器里面的ASCII文本文件。 1.解释 维基百科解释 https://en.wikipedia.org/wiki/Intel_HEX Intel HEXThe Intel HEX file is an ASCII text file with lines of text that follow the Intel HEX file format. Each line in an Intel HEX file contains one HEX record. These records are made up of hexadecimal numbers that represent machine language code and/or constant data. Intel HEX files are often used to transfer the program and data that would be stored in a ROM or EPROM. Most EPROM programmers or emulators can use Intel HEX files. 2.格式 hex行格式: :BBAAAATT 【D···D】CC 其中: : 代表行开始,固定为冒号: BB代表Bytes,数据长度 AAAA代表Address,地址 TT代表Type,数据类型(标识) D···D代表Date,数据 CC代表CheckSum,校验和 说明: BB数据长度,也就是D···D这个字段的数据长度; AAAA地址,起始地址、偏移地址,根据数据类型(TT)有关; TT数据类型(标识): 00:数据标识 01:文件结束标识 02:扩展段地址 04:线性地址 05:线性开始地址 (地址代表高16位地址,也就是要向左移16bit) CC校验和计算公式: CheckSum = 0x100 - (Sum & 0xFF) 3.例子说明 不同数据类型个行数据略有差异,先再看下00(数据类型)的格式: 一个常见hex文件: :020000040800F2:1000000000040020B1010008FD020008BD02000844:10001000F902000801020008350400080000000091:1000200000000000000000000000000021030008A4···省略数行:100470000000024084040008000000200004000086:040480004804000824:040000050800019955:00000001FF 1.04类型:线性地址行 :020000040800F2 02:数据长度,这里是(0800)地址的2字节长度; 0000:偏移地址,这里数据其实无效; 04:线性地址数据类型; 0800:线性起始地址,左移16位,即:0x0800 0000; F2:校验和 F2 = 0x100 - (0x02 + 0x04 + 0x08); 比如,修改起始地址为0600: 2.00类型:数据行 :1000000000040020B1010008FD020008BD02000844 10:数据长度,这里是16字节(程序)数据的长度; 0000:偏移地址,数据第一行偏移0000地址,第二行就是偏移0010,第二行就是偏移0020,依次偏移到FFF0; 如果偏移到FFF0,则会重新下一个起始地址,一段程序你就明白了: :10FFD000D0C5CFA20D0A00003052010810B50A4862:10FFE00002F0FEFC09A002F0FBFC14A002F0F8FCF9:10FFF0001EA221A123A002F0F3FC2CA002F0F0FC31:020000040801F1:10000000394802F0EDFC10BD3C5301080D0A2A20CE:1000100020202020202020202020202020202020E0:100020002020202020202020202020414756D6C7F5 00:线性地址数据类型; 00040020B1010008FD020008BD020008:程序数据,就是bin文件里面的纯程序数据; 44:校验和 44 = 0x100 - (0x10 + 0x04 + 0x20 + 0xB1 + 0x01 + 0x08 + 0xFD + 0x02 + 0x08 + 0xBD + 0x02 + 0x08 + 0x44) & 0xFF; 3.01类型:文件结束行 :00000001FF 00:数据长度; 0000:偏移地址,这里数据其实无效; 01:代表文件结束; FF:校验和 这里代表hex文件结束了,有些公司为了使hex传输(下载)更可靠,或通过工具(或命令在)结束行后面追加校验信息,一般远程升级会考虑更多校验信息(后期抽时间讲述一下远程升级)。 更多细节内容,可以参看链接: https://www.keil.com/support/docs/1584/ https://www.kanda.com/blog/microcontrollers/intel-hex-files-explained/ (公号不支持外链接,请复制链接到浏览器打开) 看到这里,我相信很多人都能写一个脚本工具,让hex转为bin文件(后面抽空给大家讲述一下hex和bin转换的工具)。 4. axf文件 axf格式文件是针对ARM编译器的一种格式文件,它是由 ARM 编译器产生。 axf文件除了包含程序数据(bin)和地址(hex)等数据之外,还包含调试信息。 axf文件内的调试信息附加在程序文件中,有助于分析和调试。 axf文件的调试信息作用: 可将源代码包括注释夹在反汇编代码中,这样我们可随时切换到源代码中进行调试。 还可以对程序中的函数调用情况进行跟踪(通过Watch & Call Stack Window查看)。 对变量进行跟踪(利用Watch & Call Stack Window)。 当然,axf文件调试信息的包含的内容有限,并非所有源码(及注释)相关信息都会包含在其中,想要有效调试,还是需要结合源代码工程进行调试。 5. elf文件 ELF:Executable and Linkable Format,可执行与可链接格式。 elf是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。 ---来源百度百科 elf文件和bin、hex、axf文件同样属于可执行文件这一类,但是他们之间差异还是很大,elf文件包含的信息更多,也更复杂。 elf格式文件由四部分组成: ELF header:ELF头 Program header table:程序头表 Section:节 Section header table:节头表 ELF header:描述整个文件的组织。 Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。 Section:是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出,segments与sections是包含的关系,一个segment包含若干个section。 Section Header Table: 包含了文件各个segction的属性信息。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    玩转嵌入式 嵌入式 软件开发

  • NFC和RFID的区别?为什么有些NFC无法模拟门禁卡?

    来源:嵌入式Linux 1. NFC是什么 NFC(Near Field Communication) 技术由Philips、Nokia和Sony主推的一种近距离无线通信技术(NFCIP-1),是一种短距离非接触式的通信方式,通常有效通讯距离为4厘米以内。工作频率为13.65 兆赫兹,通信速率为106kbit/秒到 848kbit/秒。通过手机为载体,把非接触式IC卡应用结合于手机中,以卡、阅读器、点对点三种应用模式,实现手机支付、行业应用、积分兑换、电子票务、身份识别、防伪、广告等多种应用的服务产品。 NFC通信总是由个发起者(initiator)和一个接受者(target)组成。 NFC也支持点到一点的通信(peer to peer) 此时参与通信的双方都有电源支持。 NFC 与其他通信技术对比:   2. RFID是什么 RFID( Radio Frequency Identification),其原理为阅读器与标签之间进行非接触式的数据通信,达到识别目标的目的。RFID 的应用非常广泛,典型应用有动物晶片、汽车晶片防盗器、门禁管制、停车场管制、生产线自动化、物料管理。 3、NFC和RFID的区别和联系 NFC和RFID在本质上没有太多的区别,但是也有一些特点需要区分的,像工作频率不同,是否支持点对点通信技术。 3.1、 工作频率 NFC的工作频率为13.56MHz,而RFID的工作频率有低频,高频(13.56MHz)及超高频。 3.2. 工作距离 NFC的工作距离理论上为0~20cm,但是在产品的实现上,由于采用了特殊功率抑制技术,使其工作距离只有0~10cm,从而更好地保证业务的安全性。由于RFID具有不同的频率,其工作距离在几厘米到几十米不等。 3.3. 工作模式 NFC同时支持读写模式和卡模式。而在RFID中,读卡器和非接触卡是独立的两个实体,不能切换。 3.4. 点对点通信 NFC支持P2P模式,RFID不支持P2P模式。 3.5. 应用领域 RFID更多的应用在生产,物流,跟踪和资产管理上,而NFC则工作在门禁,公交卡,手机支付等领域。 3.6. 标准协议 NFC的底层通讯协议兼容高频RFID的底层通信标准,即兼容ISO14443/ISO15693标准。NFC技术还定义了比较完整的上层协议,如LLCP,NDEF和RTD等。综上,尽管NFC和RFID技术有区别,但是NFC技术,尤其是底层的通信技术是完全兼容高频RFID技术的。因此在高频RFID的应用领域中,同样可以使用NFC技术。 4、NFC卡片分类 NFC卡片主要分为两类,ID卡和IC卡,从名字可以知道,IC卡里面有专门处理卡片数据的芯片,ID卡主要用来给NFC读设备读取数据。 M1卡: 全称Mifare classic 1K,普通IC卡,0扇区不可修改,其他扇区可以反复擦写。通常我们使用的门禁卡、电梯卡都是M1卡。 M1卡是NXP(恩智浦半导体)公司研发的IC卡,执行标准是ISO/IEC14443 Type A,读写频率是13.56MHz。目前大多数手机厂商使用的NFC芯片都是NXP,另一部分则是BRCM(博通)方案,均执行同一标准,这是手机读写M1卡的技术基础。 UID卡: 普通复制卡,可以反复擦写所有扇区,门禁有防火墙则失效。 CUID: 升级复制卡,可以反复擦写所有扇区,可以穿透大部分防火墙。 FUID: 高级复制卡,0扇区只能写入一次,写入后变为M1卡。 UFUID: 超高级复制卡,0扇区只能写入一次,封卡后变为M1卡,不封卡变为UID卡。 复制卡均可在网上购买,有普通卡片、钥匙扣、滴胶卡等类型,CUID通常1.5元/张,越高级的卡越贵。 4.1、M1卡的结构 M1卡标准储存的数据使用16进制,简称HEX,即由0-9、A-F组成,也写作0xAA 4.1.1 存储结构 Mifare classic 1K,即存储容量1K=1024Byte,包括16个扇区,每个扇区含4个块,每个块16Byte. 第0扇区比较特殊,0区0块前8位为厂商UID码,可以理解为M1卡的识别码。 0-2块为储存内容区间。 3块为系统保留区间,用于存放卡密码和控制码,其中: 3块前12位为keyA(密码A),3块后12位为keyB(密码B),3块中间8位为控制码。 A/B密码的默认值为12个F或0,翻译为2进制即4*12个1或0 控制码默认值为FF078069,意思是A密码(非默认情况下)不可见,B密码可见,读写验证A密码。 4.1.2 读写权限 M1卡有4种主要权限:读、写、增量、减量。 以及2种附权限:读写控制码、读写A/B密码。 每种权限都要使用A或B密码、并在控制码约束下来操作。 基于M1卡的结构及读取权限特点,M1卡又可以分为非加密、半加密、全加密三种类型。 非加密:16个扇区的A/B密码均使用默认值。 半加密:0扇区外的某一个或多个数据扇区A/B密码不是默认值。 全加密:所有扇区A/B密码不是默认值。(由于M1卡的加密逻辑已经被公开,所以所有的M1加密卡都可以被破解,破解能力PN532 5、防伪系统的原理和破解 常见的防伪系统有三种, 一是加密型,通过对扇区进行加密实现防伪。单纯的加密型已经可以通过PN532、PM3等工具完成破解。 虽然卡A/B密码可以被破解,但真正破解的重头戏是在于如何找出卡信息的存储规律,从而进行自定义修改等操作。 二是篡改型,刷卡时系统尝试写入0扇区,如果成功,则卡片作废(CUID特性,0扇区可反复擦写)。 三是滚码型,每次刷卡,系统都从特定扇区读取验证一段校验码,并写入新的校验码。如果不能通过多次刷卡找到校验码的规律,则不可复制。 6、复制卡 支持NFC的手机可以模拟白卡,如果把可以使用的卡片数据读取出来,然后再写入到手机的卡片中,就可以完成卡片的复制。 比如正常的小区门禁卡,先用一个设备把小区门禁卡里面的数据读取出来,然后再写入手机生成的这张白卡中,就完成了卡片的复制。 但是也有问题,手机生成的白卡,并不是一定可以写入所有扇区的,我的小米手机就不行,0扇区有8个字节会一直保持不变。所以就不可能用我的小米手机来模拟门禁卡,但是天无绝人之路,可以购买那种贴纸大小的NFC卡贴,复制卡片后贴在手机上,同样可以完成这样的功能。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    玩转嵌入式 RFID NFC 通信技术

发布文章