• 电磁兼容(EMC)基础知识

    配图 By 网友小野智本文思维导图:01EMC(Electro Magnetic Compatibility,电磁兼容)是指电子、电气设备或系统在预期的电磁环境中,不会因为周边的电磁环境而导致性能降低、功能丧失或损坏,也不会在周边环境中产生过量的电磁能量,以致影响周边设备的正常工作。EMI(Electro Magnetic Interference,电磁干扰):自身产生的电磁干扰不能超过一定的限值。EMS(Electro Magnetic Susceptibility,电磁抗扰度):自身承受的电磁干扰在一定的范围内。电磁环境:同种类的产品,不同的环境就有着不同的标准。需要说明的是,以上都基于一个前提:一定环境里,设备或系统都在正常运行下。02电磁干扰的产生原因:电压/电流的变化中不必要的部分。电磁干扰的耦合途径有两种:导线传导和空间辐射。导线传导干扰原因是电流总是走“最小阻抗”路径。以屏蔽线为例,低频(f10kHz)时,环路屏蔽层的感抗小于导线的阻抗,因此信号电流从屏蔽层上流过。干扰电流在导线上传输有两种方式:共模和差模。一般有用的信号为差模信号,因此共模电流只有转变为差模电流才能对有用信号产生干扰。阻抗平衡防止共模电流向差模转变,可以通过多点接地用来降低地线公共阻抗,减小共地线阻抗干扰。空间辐射干扰分近场和远场。近场又称为感应场,与场源的性质密切相关。当场源为高电压小电流时,主要表现为电场;当场源为低电压大电流时,主要表现为磁场。无论是电场还是磁场,当距离大于λ/2π时都变成了远场。远场又称为辐射场。远场属于平面波,容易分析和测量,而近场存在电场和磁场的相互转换问题,比较复杂。这里面有问题的是如果导线变成天线,有时候就分不清是传导干扰还是辐射干扰?低频带下特别是30 MHz以下的主要是传导干扰。或者可以估算当设备和导线的长度比波长短时,主要问题是传导干扰,当它们的尺寸比波长长时,主要问题是辐射干扰。干扰信号以平面电磁波形式向外辐射电磁场能量,再以泄漏和耦合形式,通过绝缘支撑物等(包括空气)为媒介,经公共阻抗的耦合进入被干扰的线路、设备或系统。举例:900MHz,平面波的转折点在50 mm电磁波辐射有两个必要条件:变化的电压/电流和辐射天线。两者缺一,都不会产生大量的辐射干扰。有些资料会给出瞬态干扰的概念,顾名思义:时间很短但幅度较大的电磁干扰。瞬态干扰一般指各类电快速脉冲瞬变(EFT)、各类浪涌(SURGE)、静电放电(ESD)等三种。03重点:消除其中任何一个因素就可以满足电磁兼容设计的要求。切断耦合途径是最有效的电磁兼容处理措施。了解下传播路径:电磁干扰可以通过电源线、信号线、地线、大地等途径传播的传导干扰,也有通过空间直接传播的空间辐射干扰。这些干扰或者噪声并不是独立存在的,在传播过程中又会出现新的复杂噪声,这种问题叠加问题才是解决问题的难点。04近场区,波阻抗与辐射源的位置、阻抗、频率及辐射源周围的介质有关;远场区,波阻抗等于电磁波传播介质的特性阻抗;在真空中,波阻抗为377Ω。由377Ω想到自由空间的特性阻抗:有个基础概念需要讲一讲:dB

    小麦大叔 EMC 电磁兼容

  • Cortex-M裸机环境下临界区保护的三种实现

    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是Cortex-M裸机环境下临界区保护的三种实现。搞嵌入式玩过 RTOS 的朋友想必都对 OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL() 这个功能代码对特别眼熟,在 RTOS 里常常会有多任务(进程)处理,有些情况下一些特殊操作(比如 XIP 下 Flash 擦写、低功耗模式切换)不能被随意打断,或者一些共享数据区不能被无序访问(A 任务正在读,B 任务却要写),这时候就要用到临界区保护策略了。所谓临界区保护策略,简单说就是系统中硬件临界资源或者软件临界资源,多个任务必须互斥地对它们进行访问。RTOS 环境下有现成的临界区保护接口函数,而裸机系统里其实也有这种需求。在裸机系统里,临界区保护主要就是跟系统全局中断控制有关。痞子衡之前写过一篇 《嵌入式MCU中通用的三重中断控制设计》,文中介绍的第三重也是最顶层的中断控制是系统全局中断控制,今天痞子衡就从这个系统全局中断控制使用入手给大家介绍三种临界区保护做法:一、临界区保护测试场景关于临界区保护的测试场景无非两种。第一种场景是受保护的多个任务间并无关联,也不会互相嵌套,如下面的代码所示,task1 和 task2 是按序被保护的,因此 enter_critical() 和 exit_critical() 这两个临界区保护函数总是严格地成对执行:void critical_section_test(void){    // 进入临界区    enter_critical();    // 做受保护的任务1    do_task1();    // 退出临界区    exit_critical();    // 进入临界区    enter_critical();    // 做受保护的任务2,与任务1无关联    do_task2();    // 退出临界区    exit_critical();}第二种场景就是多个任务间可能有关联,会存在嵌套情况,如下面的代码所示,task2 是 task1 的一个子任务,这种情况下,你会发现实际上是先执行两次 enter_critical(),然后再执行两次 exit_critical()。需要注意的是 task1 里面的子任务 task3 虽然没有像子任务 task2 那样被主动加一层保护,但由于主任务 task1 整体是受保护的,因此子任务 task3 也应该是受保护的。void do_task1(void){    // 进入临界区    enter_critical();    // 做受保护的任务2,是任务1中的子任务    do_task2();    // 退出临界区    exit_critical();     // 做任务3    do_task3();}void critical_section_test(void){    // 进入临界区    enter_critical();    // 做受保护的任务1    do_task1();    // 退出临界区    exit_critical();}二、临界区保护三种实现上面的临界区保护测试场景很清楚了,现在到 enter_critical()、exit_critical() 这对临界区保护函数的实现环节了:2.1 入门做法首先是非常入门的做法,直接就是对系统全局中断控制函数 __disable_irq()、__enable_irq() 的封装。回到上一节的测试场景里,这种实现可以很好地应对非嵌套型任务的保护,但是对于互相嵌套的任务保护就失效了。上一节测试代码里,task3 应该也要受到保护的,但实际上并没有被保护,因为紧接着 task2 后面的 exit_critical() 直接就打开了系统全局中断。void enter_critical(void){    // 关闭系统全局中断    __disable_irq();}void exit_critical(void){    // 打开系统全局中断    __enable_irq();}2.2 改进做法针对入门做法,可不可以改进呢?当然可以,我们只需要加一个全局变量 s_lockObject 来实时记录当前已进入的临界区保护的次数,即如下代码所示。每调用一次 enter_critical() 都会直接关闭系统全局中断(保证临界区一定是受保护的),并记录次数,而调用 exit_critical() 时仅当当前次数是 1 时(即当前不是临界区保护嵌套情况),才会打开系统全局中断,否则只是抵消一次进入临界区次数而已。改进后的实现显然可以保护上一节测试代码里的 task3 了。static uint32_t s_lockObject;void init_critical(void){    __disable_irq();    // 清零计数器    s_lockObject = 0;    __enable_irq();}void enter_critical(void){    // 关闭系统全局中断    __disable_irq();    // 计数器加 1     s_lockObject;}void exit_critical(void){    if (s_lockObject 

    小麦大叔 Cortex-M

  • 那些常见的电路接口以及电子符号

    推荐关注下方公众号学习更多嵌入式知识! 分享一些常用的电子接口以及部分电子元器件符号,这些你都见过嘛?

    玩转嵌入式 电子 接口 电路

  • 咱们是时候改变一下嵌入式软件开发思维方式了!

    作者 | Elektor 译者 | 禾沐 国内嵌入式软件开发一直比较传统,除涉及关键系统外的多数项目,都是在“编程”优先的开发方式驱动下实施的。 这背后的原因有很多,除了产品上市压力大、建模和仿真工具价格不菲之外,还有一个重要因素——嵌入式软件开发的思维方式转变需要漫长时间和教育过程才能完成。 本篇译文详细阐述了如何改变嵌入式软件开发思维方式,并结合几款很实用的工具和汽车电子实例进行了具体分析。 嵌入式软件开发者们总是喜欢躲开关于软件架构、抽象化、建模和模拟的讨论,直接开始进行编程,而嵌入式软件代码往往一旦发布就不能进行改动了,出错的余地非常小。 既然我们都知道测试对嵌入式软件非常重要,也有很多进行建模/仿真的工具,那么,为什么还会先编程,然后等待很久才开始测试呢? 啄木鸟带来的危险 那是一次“原来是这样!”的顿悟。当时正在为了一个培训课程进行准备,我在想,为什么嵌入式软件工程师如此不愿意使用在其他软件开发过程中常见的概念和方法呢? 在微控制器相关的工程中,我时常会遇到这样的工程师:他们既不定义代码风格标准(尽管软件开发有多个工程师参与),也不会定义任何帮助提高代码可移植性和可用性的设计准则,更不会关心面向对象设计、建模和仿真。 我的脑海中浮现出了下面这句话:“如果木匠用程序员编程的方法建造建筑,只需要一只啄木鸟就可以摧毁整个人类文明。” 这句话从温伯格法则稍微修改而来,它着实引起了我的共鸣。为什么嵌入式软件开发者们不借鉴计算机科学研究成果,使用正确的方式构建代码,或者借力建模/仿真技术呢? 编程太快了吗? 一个原因可能是来自这一代程序员的经历。他们的生涯从资源受限的8位微控制器开始,用汇编语言最高程度地利用硬件的性能是王道,抽象化和工具自动生成的代码只能意味着代码冗余和失去对代码的完全控制。转移到C语言对于一些人来说都有难度,尽管C语言已经非常贴近硬件了。 另一个角度是C语言的教学方式往往注重语法,而不是如何最佳地使用这门语言。但是,就像自然语言一样,精通语法并不能让你成为一位演说家。 物联网(IoT)应用出现以前,升级微控制器的固件基本意味着亲自前往设备的所在地,代码出错的余地非常小。即便如此,许多嵌入式程序员的开发方式也始终没有改变。 推广新的开发方式? 过去,运行在微控制器上的代码往往可以用一个简单的状态机表示,如今微控制器需要解决多维的问题。今天的微控制器可以通过磁场导向控制(FOC)技术对电动机进行换向操作,并同时运行其他任务。 虽然相关的代码已经存在,但是,实时确定电动机的角度和计算下次换向的时机就已经非常复杂了。 这样的微控制器如果出现在家用洗衣机中的话,我们还需要考虑IEC 60730标准(自动电子控制 – 第1部分:一般要求)。大多数微控制器厂商会提供能够执行CPU寄存器、程序计数器、内存完整性和时钟测试的“B类”库,库代码应该在运行实时电动机控制的同时执行。 系统中一般还会有能快速响应的通讯接口和人机交互界面,嵌入式开发者也许可以保证每个部分都能单独运行,但是整合在一起后系统是否依然可靠呢? 一个选项是用统一建模语言(UML)针对应用进行建模并运行仿真。嵌入式软件开发者往往非常不喜欢这种方式,他们认为这样做是浪费时间:如果有时间建模和运行仿真,还不如直接把代码写出来。 一些人还在系统设计中滥用UML,导致UML染上了不好的名声。抽象的设计方式还意味着不同的开发过程,说服一个开发团队转变开发过程并不是一件容易的事情。 对于在设计和实现中各编程一次的担心,我们称为过程的不连续性(phase discontinuity)。理想状况下,建模过程应该解决这一问题,比如实时面向对象建模(ROOM)领域特定语言(DSL)。 ROOM专门针对事件驱动的实时系统,它支持建模和模拟,还能够生成针对目标硬件的代码。 ROOM从三个维度描述软件:结构、行为和继承。结构可以用角色(actor)通过端口(port)互相通信表示,消息通过运行时软件库进行传递。角色的行为通过多层有限状态机进行表示,并可以图表形式输出。 进入和离开状态的代码可以用代码生成器(比如针对微控制器的C编译器)的目标语言进行定义。当端口接收消息或者收到回应时,状态改变会发生。 继承提供了ROOM中面向对象的部分,既将角色看作可以多次实例化的类。每个实例继承状态机,需要的话,状态机还可以进一步扩展。最后一个概念是分层(layering),它允许应用互相进行通信,或者通过服务访问点(SAP)使用服务(比如定时器)。 Eclipse集成开发环境插件eTrice支持这一软件开发方式。模型可以通过图形或者文本创建,并可以输出C、C 或者Java代码。在模型上运行模拟将会输出消息序列图(MSC),UML工具Trace2UML(Astade[8]的一部分)可以将序列图进行可视化。 图1所示的是一个简单的“乒乓” 样例应用,其中定时器SAP决定了响应延迟的时间。通过图像和ROOM DSL可以检查应用的结构和行为。执行模型将会在命令行上输出结果,模拟的输出是MSC(见图2)。 图1  eTrice“乒乓”样例工程,可以看到“乒”和“乓”的角色(左)以及它们的行为(右) 图2   在“乒乓”模型上运行模拟所产生的消息序列图 为什么测试开始得很晚? 在匆匆开始编程之后,许多嵌入式开发团队往往会等到已经编写了不少代码后才考虑开始测试问题,这样做的背后有很多繁杂的原因,比如错误地认为测试必须由一个团队或者部门负责,或是缺乏清晰、易于测试的软件需求。 集成开发环境强大的调试器也可能让我们产生了虚假的安全感。 Lisa Simone在《If I Only Changedthe Software, Why is the Phone on Fire?》一书[9]中用幽默的方式探讨了程序错误带来的后果。在一个例子中,不当的数据类型导致了温度控制算法的错误。 虽然Simone完整地描述了寻找这类问题根源的过程,但事实上,这些问题在单元测试中就应该被发现。 单元测试是一种白盒风格测试,即编写测试的工程师有源代码的知识,往往编写者就是源代码的作者。在微控制器上,单元测试需要一个能够执行测试和分析输出的应用,最终结果往往通过串口输出到控制台上。 这意味着,我们需要能够执行测试的硬件平台。Arduino和其他类似的平台提供UART到USB转换,这让在控制台上输出测试结果变得更容易。但是,针对每个软件模块开发和维护一个测试应用确实是很大的工作量。 iSYSTEM开发的testIDEA提供了显著降低测试难度的方法。iSYSTEM开发的调试工具意味着开发环境不仅能访问源代码,还可以通过调试接口访问微控制器的环境。使用testIDEA只需要提供能够写入微控制器闪存的编译过的应用代码(ELF格式)。 在testIDEA中定义的测试用预定义的参数单独测试函数(图3)。在软件中我们能够任意修改RAM,从而注入任何类型的数据和指针。 测试的通过与否取决于函数的的返回值是否正确,测试中还可以通过跟踪功能提供代码覆盖的数据。 工具中有对C 的支持,但是在测试各类方法之前需要先调用构造函数。测试还可以输出进入Python环境,帮助测试的自动化。 图3   通过testIDEA测试在微控制器上运行的软件函数 testIDEA的一大功能是所有内部和私有变量值都可以在测试时进行注入,当你调试时可以想做什么就做什么。 这对于在编译中优化过的代码非常有帮助:我们都知道无用代码的移除和代码段的重组意味着优化后代码的行为和优化前并不完全一致,换言之,代码和二进制代码的关系会变得模糊,这让调试变得更加有挑战性。 通过在testIDEA中运行同样的测试,你可以在进行系统整合前确定基础代码的行为没有变化。 多少测试才足够? 对初次接触测试的工程师来说,确定最佳的策略并不简单。像《爱丽丝梦游仙境》中的兔子洞一深入研究测试的方法,你会觉得需要测试的东西越来越多。 软件的多维性和复杂的依赖关系意味着我们很难预估多少测试是足够的。参与过安全关键项目的工程师都知道,相关的标准都不是绝对的,比如汽车相关的ISO 26262和医疗相关的ISO 14971、IEC 60601。 举例来说,ISO 26262“非常推荐”代码分支覆盖作为测试覆盖的基准,然后只是“推荐”语句覆盖和MC/DC(修改条件、决策覆盖)。只有和测试领域的专家合作,才能确定在你的应用中如何测试才是最佳的。 为了确保代码的全部分支都被覆盖,所有可能的代码组合都要被测试到,我们可以借助sepp.med GmbH公司的MBTsuite产品。其基于模型的测试框架能够帮助确定所有可能的测试组合,因而非常适合系统集成和测试。 确定测试范围的第一步是将系统需求输入到Enterprise Architect这类的工具中。接下来构造系统的模型,将其和系统需求链接在一起。设计中较为复杂的部分可以用子模型来表示,从而保持模型整体的抽象程度和简洁性(见图4和图5)。 图4   电子驻车制动装置的EnterpriseArchitect模型 图5  释放刹车部分的Enterprise Architect子模型,可以看到模型到要求间的链接 接下来将完成的模型导入到MBTsuite中。软件允许你定义不同的测试策略,全路径覆盖(Full Path Coverage)会生成覆盖整个模型的测试方案,最短路覆盖(Shortest Path)用最短的路径遍历模型(见图6)。 此外,随机策略可以帮助发现形式化策略可能无法发现的问题,也可以作为在目标硬件上验证测试机制的冒烟测试(smoke test )的一部分。 图6    MBTsuite中电子驻车刹车的模型产生全路径覆盖测试方案 从电子驻车刹车(EPB)的模型(见图4)可以看出,遍历节点的过程中可能会进入无限循环。循环数目(number of loop)和最长路径长度(maximum path length)参数可以用来避免这一情况的产生。你也可以通过指定模型的区域来生成针对特定功能的测试。 MBTsuite最简单的用法是产生Word或Excel文档详述测试的步骤和各个验证点预期的结果,手动测试时可以勾选每一步的完成情况,最终给出通过/失败的结果。自动化测试的情形下,软件根据模板生成合适的格式,比如Python代码。 在硬件上快速测试 理想条件下,应用在开发过程中应该进行硬件在环(HIL)测试,这对于在高压、高电流或者有活动部件的环境中运行的应用尤其重要。 但是,这样的测试机制可能会很昂贵,许多团队也可能需要在同一套硬件上测试多种产品。其结果是HIL测试的瓶颈效应导致测试直到开发末期才进行。在当前新冠疫情的影响下,HIL测试甚至可能完全无法进行。 为了应对这一挑战,PROTOS Software GmbH开发了miniHIL平台。平台包括一个A4纸大小的硬件板,其上右侧是安装STM32 Nucleo开发板的位置,左侧有一个强力的STM32H743微控制器,中间则是连接两侧的针脚矩阵。 STM32H743的作用是模拟STM32 Nucleo控制的设备,并产生同样的信号。这一平台非常适用于在没有实体洗衣机或者电钻的情况下测试电动机控制应用。 miniHIL环境支持eTrice和CaGe(Case Generator)语言进行测试的开发。这既让开发者可以快速检查代码改动的正确性,也允许整个平台和持续集成(CI)平台相结合,比如Jenkins。这样一来,自动测试可以定期在硬件上执行,比如每晚执行测试,第二天一早在仪表盘上检查结果(见图7)。 图7   miniHIL和运行Jenkins持续集成平台服务器的PC整合(来源:PROTOS SoftwareGmbH) 寻找偶发失效 今天的汽车应用非常复杂,在一辆汽车上运行的代码量和Windows NT类似。代码开发的分布性意味着很多供应商(甚至客户)都参与到了其中。偶发的失效往往和定时有关,而不是功能,多核处理器和虚拟机的广泛应用更是加剧了这一情况。 即使在测试充分的情况下,即将发货的硬件上,甚至是客户环境中,奇怪的事情还是时而发生。一个汽车电子的实例:两个通过CAN总线相连的电子控制单元(ECU)会偶发错误,ECU A和B连接偶尔会出错,但是和C连接不会,ECU B和C能正常协同工作。 问题的根本原因是ECU石英频率的微小不同,运行时间足够长的情况下,两个微控制器的时钟差异会导致CAN消息的偶尔丢失。 Inchron AG[17]的chronSIM和chronVIEW,以及Vector InfomatikGmbH的TA Tool Suite等工具将定时看得和功能一样重要。它们允许在编写任何代码前在目标系统上构建模型,从而帮助系统架构师针对多种不同的硬件设计最佳的系统架构。 这些工具会分析在不同处理器核上运行代码的影响,这对于在核心频率不同的异构多核处理器上运行的应用而言至关重要。最后,工具分析事件链(见图8),并测量事件从传感器到达系统,并最终到达执行器的时间。这些工作对于整合若干数据速率不同的传感器数据的自动驾驶系统而言尤为重要。 图8  Inchron Tool Suite通过建模、仿真和分析等方法突出定时的重要性,图中展示的是一个事件链的分析(来源:INCHRON AG) 通过定义代码段的超时时间,以及同时注意功能和定时特性,与时间相关的ECU通过CAN连接的问题可以在设计层面上解决。在模型中,可以理解时钟频率变动、核心分配、代码执行时间和时钟频率变化的影响。 这些工具还可以分析在实际硬件上运行的微控制器产生的跟踪数据,检查在不同测试条件下代码段的执行时间是否合乎需求。 是更加重视建模的时候了? 嵌入式软件开发领域一直比较传统。“急于编程”的思维方式被微控制器供应商提供的编程和调试工具链所加剧,这些工具多是免费的,这样一来其他付费授权的工具链就失去了存在的意义。 支持嵌入式系统建模和仿真的工具提供了一个在开始编程前后退一步、进一步抽象化的机会。这一方式的背后是重复性的分析和测试,它因此可以帮助在开发太过深入前以较低的成本纠正设计上的问题。 桌面HIL还允许通过压缩的测试周期快速验证小的代码改动,而不是在整合后的代码上运行完整的测试。最后,虽然最复杂的实时应用(比如汽车)开发中建模和测试手段已经被广为应用,我们还是应该在功能要求的基础上添加定时的要求。 采用这些基于模型的工具的最大挑战,是对改变的畏惧。它们的抽象性让传统嵌入式开发者不得不离开他们熟悉和喜欢的硬件,前往一个陌生的地方。它们还需要开发过程做出改变,这导致了很多负面的意见。 这些工具从纸面上看非常合理,但只有真正开始使用,我们才能知道在实际操作中它们带来的益处。许多此类工具已经存在了十年或者更久,它们的地位和作用已经建立起来了。 这些工具可以降低开发时间和开销、提高产品的质量,开发者们需要从实际的开发过程出发,分析这类选项是否适用于他们的产品。 相关链接 [1]https://quoteinvestigator.com/2019/09/19/woodpecker/. [2]IEC 60730-1:2013. https://webstore.iec.ch/publication/3117. [3]例如Hitex B类库:http://www2.hitex.com/dl-classb-sam-d2x. [4]A. E. Bell. Death by UML Fever. https://queue.acm.org/detail.cfm?id=984495. [5]B. Selic, G. Gullekson, P. Ward, Real-TimeObject-Oriented Modeling, Wiley, 1994. [6]https://en.wikipedia.org/wiki/Real-Time_Object-Oriented_Modeling. [7]https://www.eclipse.org/etrice/downloads/. [8]https://wiki.astade.de/dokuwiki/doku.php?id=start. [9]L. Simone, If I Only Changed the Software,Why is the Phone on Fire?, Newnes, 2007. [10]https://www.isystem.com/products/software/testidea.html. [11]Feabhas Ltd, “A Quick Guide to ISO 26262,”2016. https://www.feabhas.com/sites/default/files/2016-06/A quick guide to ISO 26262[1]_0_0.pdf. [12]https://mbtsuite.com/. [13]https://sparxsystems.com/. [14]https://docs.protossoftware.de/minihil/latest/Reference/CaGe/index.html. [15]https://www.jenkins.io/. [16]https://www.visualcapitalist.com/millions-lines-of-code/. [17]https://www.inchron.com/. [18]https://www.vector.com/int/en/products/products-a-z/software/ta-tool-suite/. 往期推荐: 往期推荐 实用 | 一个简单易用的菜单框架 实用 | 手头上无LCD却又急着开发UI?LCD模拟器了解一下~ 基于EasyX写的一个小应用,源码拿走不谢~ 分享一款嵌入式人必备绘图工具,让你的工作汇报更精彩!(附安装、踩坑、填坑教程) 基于vs2019的lvgl模拟器使用 wireshark抓包工具的使用及分析

    嵌入式大杂烩 软件开发 嵌入式软件

  • 改变嵌软开发思维方式之:状态机的三种实现方法

    作者 | Alicedodo 上一篇推文:咱们是时候改变一下嵌入式软件开发思维方式了!提到了状态机。在之前的推文中:干货 | 嵌入式之状态机编程。有简单介绍了状态机的示例。这次我们一起来学习C语言实现状态机的三种方法解析。 状态机的实现无非就是 3 个要素:状态、事件、响应。转换成具体的行为就 3 句话。 发生了什么事? 现在系统处在什么状态? 在这样的状态下发生了这样的事,系统要干什么? 用 C 语言实现状态机主要有 3 种方法:switch—case 法、表格驱动法、函数指针法。 switch—case 法 状态用 switch—case 组织起来, 将事件也用switch—case 组织起来, 然后让其中一个 switch—case 整体插入到另一个 switch—case 的每一个 case 项中  。 「程序清单 List4  :」 switch(StateVal) { case S0: switch(EvntID) { case E1: action_S0_E1(); /*S0 状态下 E1 事件的响应*/ StateVal = new state value;/*状态迁移,不迁移则没有此行*/ break; case E2: action_S0_E2(); /*S0 状态下 E2 事件的响应*/ StateVal = new state value; break; ...... case Em: action_S0_Em(); /*S0 状态下 Em 事件的响应*/ StateVal = new state value; break; default: break; } break; case S1: ...... break; ...... case Sn: ...... break; default: break; } 上面的伪代码示例只是通用的情况,实际应用远没有这么复杂。虽然一个系统中事件可能有很多种,但在实际应用中,许多事件可能对某个状态是没有意义的。 例如在程序清单 List4中,如果 E2、······ Em 对处在 S0 状态下的系统没有意义,那么在 S0 的 case 下有关事件E2、······ Em 的代码根本没有必要写,状态 S0 只需要考虑事件 E1 的处理就行了。 既然是两个 switch—case 之间的嵌套, 那么就有一个谁嵌套谁的问题, 所以说 switch—case法有两种写法:状态嵌套事件和事件嵌套状态。这两种写法都可以, 各有利弊, 至于到底选用哪种方式就留给设计人员根据具体情况自行决断吧。 关于 switch—case 法还有最后一点要说明, 因为 switch—case 的原理是从上到下挨个比较,越靠后,查找耗费的时间就越长,所以要注意状态和事件在各自的 switch 语句中的安排顺序,不推荐程序清单 List4 那样按顺序号排布的方式。出现频率高或者实时性要求高的状态和事件的位置应该尽量靠前。 表格驱动法 如果说 switch—case 法是线性的,那么表格驱动法则是平面的。表格驱动法的实质就是将状态和事件之间的关系固化到一张二维表格里, 把事件当做纵轴,把状态当做横轴,交点[Sn , Em]则是系统在 Sn 状态下对事件 Em 的响应  。 如图 4, 我把表格中的 Node_SnEm 叫做状态机节点, 状态机节点 Node_SnEm 是系统在 Sn状态下对事件 Em 的响应。这里所说的响应包含两个方面:输出动作和状态迁移。状态机节点一般是一个类似程序清单 List5 中的结构体变量 。 struct fsm_node { void (*fpAction)(void* pEvnt); INT8U u8NxtStat; }; 程序清单 List5 中的这个结构体有两个成员:fpAction 和 u8NxtStat。fpAction 是一个函数指针, 指向一个形式为 void func(void * pEvnt)的函数, func 这个函数是对状态转移中动作序列的标准化封装。 也就是说, 状态机在状态迁移的时候, 不管输出多少个动作、操作多少个变量、调用多少个函数,这些行为统统放到函数 func 中去做。 把动作封装好了之后,再把封装函数 func 的地址交给函数指针 fpAction,这样,想要输出动作,只需要调用函数指针 fpAction 就行了。 再看看上面的 func 函数,会发现函数有一个形参 pEvnt,这是一个类型为 void * 的指针, 在程序实际运行时指向一个能存储事件的变量,通过这个指针我们就能获知关于事件的全部信息,这个形参是很有必要的。事件一般包括两个属性:事件的类型和事件的内容。 例如一次按键事件,我们不仅要知道这是一个按键事件,还要知道按下的到底是哪个键。事件的类型和状态机当前的状态可以让我们在图 4 的表格中迅速定位,确定该调用哪个动作封装函数, 但是动作封装函数要正确响应事件还需要知道事件的内容是什么, 这也就是形参pEvnt 的意义。 由于事件的多样性,存储事件内容的数据格式不一定一样,所以就把 pEvnt 定义成了 void * 型,以增加灵活性。有关 fpAction 的最后一个问题:如果事件 Em 对状态 Sn 没有意义,那么状态机节点Node_SnEm 中的 fpAction 该怎么办?我的答案是:那就让它指向一个空函数呗!前面不是说过么,什么也不干也叫响应。 u8NxtStat 存储的是状态机的一个状态值。我们知道, 状态机响应事件要输出动作, 也就是调用函数指针 fpAction 所指向的那个封装函数, 函数调用完毕后程序返回主调函数, 状态机对事件的响应就算结束了, 下一步就要考虑状态迁移的问题了。 可能要保持本状态不变, 也可能要迁移到一个新的状态,该如何抉择呢?u8NxtStat 存储的状态就是状态机想要的答案! 图 4 的这张表格反映在 C 语言代码里就是一个二维数组,第 1 维就是状态机的状态,第 2维就是统一分类的事件,而数组的元素则是程序清单 List5 中的结构体常量。如果程序中使用表格驱动法,还需要注意一些特别的事项。要将状态当做表格的横轴,那么就要求状态值集合必须满足以下条件: (1) 该集合是一个递增的等差整数数列 (2) 该数列初值为 0 (3) 该数列等差值为 1 “事件” 作为纵轴,其特点和要求与用来做横轴的“状态” 完全一致。在 C 语言提供的数据类型中, 没有比枚举更符合以上要求的可选项了, 极力推荐将状态集合和事件类型集合做成枚举常量。表格驱动法的优点:调用接口统一 ,定位快速。 表格驱动法屏蔽了不同状态下处理各个事件的差异性,因此可以将处理过程中的共性部分提炼出来,做成标准统一的框架式代码,形成统一的调用接口。根据程序清单 List5 中的状态机节点结构体,做成的框架代码如程序清单 List6 所示。 表格驱动法查找目标实际上就是一次二维数组的寻址操作,所以它的平均效率要远高于switch—case 法。 「程序清单 List6  :」 extern struct fsm_node g_arFsmDrvTbl[][]; /*状态机驱动表格*/ INT8U u8CurStat = 0; /*状态暂存*/ INT8U u8EvntTyp = 0; /*事件类型暂存*/ void* pEvnt = NULL; /*事件变量地址暂存*/ struct fsm_node stNodeTmp = {NULL, 0}; /*状态机节点暂存*/ u8CurStat = get_cur_state(); /*读取当前状态*/ u8EvntTyp = get_cur_evnt_typ(); /*读取当前触发事件类型*/ pEvnt = (void*)get_cur_evnt_ptr(); /*读取事件变量地址*/ stNodeTmp = g_arFsmDrvTbl[u8CurStat ][u8EvntTyp ];/*定位状态机节点*/ stNodeTmp.fpAction(pEvnt ); /*动作响应*/ set_cur_state(stNodeTmp.u8NxtStat); /*状态迁移*/ ..... 表格驱动法好则好矣,但用它写出来的程序还有点儿小问题,我们先来看看按照表格驱动法写出来的程序有什么特点 。 前面说过,表格驱动法可以把状态机调度的部分做成标准统一的框架代码,这个框架适用性极强, 不管用状态机来实现什么样的应用, 框架代码都不需要做改动, 我们只需要根据实际应用场合规划好状态转换图,然后将图中的各个要素(状态、事件、动作、迁移,有关“条件”要素一会儿再说)用代码实现就行了,我把这部分代码称作应用代码。 在应用代码的.c 文件中, 你会看到一个声明为 const 的二维数组, 也就是图 4 所示的状态驱动表格, 还会看到许多彼此之间毫无关联的函数, 也就是前面提到的动作封装函数。这样的一份代码, 如果手头上没有一张状态转换图, 让谁看了也会一头雾水, 这样的格式直接带来了代码可读性差的问题。 如果我们想给状态机再添加一个状态,反映到代码上就是给驱动表格再加一列内容,同时也要新添加若干个动作封装函数。如果驱动表格很大, 做这些工作是很费事儿的, 而且容易出错。如果不小心在数组中填错了位置, 那么程序跑起来就和设计者的意图南辕北辙了, 远没有在 switch—case 法中改动来得方便、安全。上面说的只是小瑕疵, 其实最让我不爽的是表格驱动法不能使用干货 | 嵌入式之状态机编程 中 g_stFSM那样的 Extended State Machine(对这个词组还有印象吧?)!Extended State Machine 的最大特点就是状态机响应事件之前先判断条件,根据判定结果选择执行哪些动作,转向哪个状态。 也就是说,系统在状态 Sn 下发生了事件 Em 后,转向的状态不一定是唯一的,这种灵活性是 Extended State Machine 的最有价值的优点。 回过头来看看程序清单 List5 中给出的状态机节点结构体,如果系统在状态 Sn 下发生了事件 Em, 状态机执行完 fpAction 所给出的动作响应之后, 必须转到 u8NxtStat 指定的状态。 表格驱动法的这个特性直接杜绝了 Extended State Machine 在表格驱动法中应用的可能性, 所以表格驱动法的代码实现中不存在“条件” 这个状态机要素。ESM,你是如此的优秀,我怎么舍得抛弃你 ?! 再看图 4 所示的表格驱动法示例图,如果我们把表格中的代表事件的纵轴去掉,只留下代表状态的横轴,将一列合并成一格,前文提到的问题是不是能得到解决呢?不错!这就是失传江湖多年的《葵花宝典》 ——阉割版表格驱动法 !! 阉割版表格驱动法,又名压缩表格驱动法,一维状态表格与事件 switch—case 的合体。压缩表格驱动法使用了一维数组作为驱动表格,数组的下标即是状态机的各个状态。 表格中的元素叫做压缩状态机节点, 节点的主要内容还是一个指向动作封装函数的函数指针, 只不过这个动作封装函数不是为某个特定事件准备的, 而是对所有的事件都有效的。 节点中不再强制指定状态机输出动作完毕后所转向的状态, 而是让动作封装函数返回一个状态, 并把这个状态作为状态机新的状态。 压缩表格驱动法的这个特点, 完美的解决了 Extended State Machine 不能在表格驱动法中使用的问题 。 程序清单 List7 中的示例代码包含了压缩状态机节点结构体和状态机调用的框架代码。 「程序清单 List7:」 struct fsm_node /*压缩状态机节点结构体*/ { INT8U (*fpAction)(void* pEvnt); /*事件处理函数指针*/ INT8U u8StatChk; /*状态校验*/ }; ...... u8CurStat = get_cur_state(); /*读取当前状态*/ ...... if(stNodeTmp.u8StatChk == u8CurStat ) { u8CurStat = stNodeTmp.fpAction(pEvnt ); /*事件处理*/ set_cur_state(u8CurStat ); /*状态迁移*/ } else { state_crash(u8CurStat ); /*非法状态处理*/ } ..... 对照程序清单 List5,就会发现程序清单 List7 中 struct fsm_node 结构体的改动之处。首先, fpAction 所指向函数的函数形式变了,动作封装函数 func 的模样成了这样的了: INT8U func(void * pEvnt); 现在的动作封装函数 func 是要返回类型为 INT8U 的返回值的,这个返回值就是状态机要转向的状态, 也就是说, 压缩表格驱动法中的状态机节点不负责状态机新状态的确定, 而把这项任务交给了动作封装函数 func, func 返回哪个状态, 状态机就转向哪个状态。 新状态由原来的常量变成了现在的变量,自然要灵活许多。上面说到现在的动作封装函数 func 要对当前发生的所有的事件都要负责, 那么 func 怎么会知道到底是哪个事件触发了它呢?看一下 func 的形参 void * pEvnt 。 在程序清单 List5 中我们提到过,这个形参是用来向动作封装函数传递事件内容的,但是从前文的叙述中我们知道, pEvnt 所指向的内存包含了事件的所有信息, 包括事件类型和事件内容 , 所以通过形参 pEvnt , 动作封装函数 func 照样可以知道事件的类型。 程序清单 List7 中 struct fsm_node 结构体还有一个成员 u8StatChk , 这里面存储的是状态机 的一个状态,干什么用的呢?玩 C 语言数组的人都知道,要严防数组寻址越界。 要知道,压缩表格驱动法的驱动表格是一个以状态值为下标的一维数组, 数组元素里面最重要的部分就是一个个动作封装函数的地址。 函数地址在单片机看来无非就是一段二进制数据, 和内存中其它的二进制数据没什么两样,不管程序往单片机 PC 寄存器里塞什么值,单片机都没意见。假设程序由于某种意外而改动了存储状态机当前状态的变量,使变量值变成了一个非法状态。 再发生事件时, 程序就会用这个非法的状态值在驱动表格中寻址, 这时候就会发生内存泄露,程序拿泄露内存中的未知数据当函数地址跳转,不跑飞才怪! 为了防止这种现象的发生, 压缩状态机节点结构体中又添加了成员 u8StatChk 。u8StatChk中存储的是压缩状态机节点在一维驱动表格的位置, 例如某节点是表格中的第 7 个元素, 那么这个节点的成员 u8StatChk 值就是 6。 看一下程序清单 List7 中的框架代码示例, 程序在引用函数指针 fpAction 之前, 先检查当前状态和当前节点成员 u8CurStat 的值是否一致,一致则认为状态合法,事件正常响应,如果不一致,则认为当前状态非法,转至意外处理,最大限度保证程序运行的安全。当然,如果泄露内存中的数据恰好和 u8CurStat 一致,那么这种方法真的就回天乏力了。 还有一个方法也可以防止状态机跑飞,如果状态变量是枚举,那么框架代码就可以获知状态值的最大值, 在调用动作封装函数之前判断一下当前状态值是否在合法的范围之内, 同样能保证状态机的安全运行。 压缩表格驱动法中动作封装函数的定义形式我们已经知道了,函数里面到底是什么样子的呢?程序清单 List8 是一个标准的示例。 「程序清单List8:」 INT8U action_S0(void* pEvnt) { INT8U u8NxtStat = 0; INT8U u8EvntTyp = get_evnt_typ(pEvnt); switch(u8EvntTyp ) { case E1: action_S0_E1(); /*事件 E1 的动作响应*/ u8NxtStat = new state value; /*状态迁移,不迁移也必须有本行*/ break; ...... case Em: action_S0_Em(); /*事件 Em 的动作响应*/ u8NxtStat = new state value; /*状态迁移,不迁移也必须有本行*/ break; default: ; /*不相关事件处理*/ break; } return u8NxtStat ; /*返回新状态*/ } 从程序清单 List8 可以看出, 动作封装函数其实就是事件 switch—case 的具体实现。函数根据形参 pEvnt 获知事件类型, 并根据事件类型选择动作响应, 确定状态机迁移状态, 最后将新的状态作为执行结果返回给框架代码。 有了这样的动作封装函数, Extended State Machine 的应用就可以完全不受限制了!到此,有关压缩表格驱动法的介绍就结束了。 个人认为压缩表格驱动法是相当优秀的,它既有表格驱动法的简洁、高效、标准,又有 switch—case 法的直白、灵活、多变,相互取长补短,相得益彰。 函数指针法 上面说过,用 C 语言实现状态机主要有 3 种方法(switch—case 法、表格驱动法、函数指针法), 其中函数指针法是最难理解的, 它的实质就是把动作封装函数的函数地址作为状态来看待。不过,有了之前压缩表格驱动法的铺垫,函数指针法就变得好理解了,因为两者本质上是相同的。 压缩表格驱动法的实质就是一个整数值(状态机的一个状态)到一个函数地址(动作封装函数)的一对一映射, 压缩表格驱动法的驱动表格就是全部映射关系的直接载体。在驱动表格中通过状态值就能找到函数地址,通过函数地址同样能反向找到状态值。 我们用一个全局的整型变量来记录状态值,然后再查驱动表格找函数地址,那干脆直接用一个全局的函数指针来记录状态得了,还费那劳什子劲干吗?!这就是函数指针法的前世今生。 用函数指针法写出来的动作封装函数和程序清单 List8 的示例函数是很相近的, 只不过函数的返回值不再是整型的状态值, 而是下一个动作封装函数的函数地址, 函数返回后, 框架代码再把这个函数地址存储到全局函数指针变量中。 相比压缩表格驱动法,在函数指针法中状态机的安全运行是个大问题,我们很难找出一种机制来检查全局函数指针变量中的函数地址是不是合法值。如果放任不管, 一旦函数指针变量中的数据被篡改,程序跑飞几乎就不可避免了。 小节 有关状态机的东西说了那么多,相信大家都已经感受到了这种工具的优越性,状态机真的是太好用了!其实我们至始至终讲的都是有限状态机(Finite State Machine 现在知道为什么前面的代码中老是有 fsm 这个缩写了吧!), 还有一种比有限状态机更 NB 更复杂的状态机, 那就是层次状态机(Hierarchical State Machine 一般简写为 HSM)。 通俗的说,系统中只存在一个状态机的叫做有限状态机,同时存在多个状态机的叫做层次状态机(其实这样解释层次状态机有些不严谨, 并行状态机也有多个状态机, 但层次状态机各个状态机之间是上下级关系,而并行状态机各个状态机之间是平级关系)。 层次状态机是一种父状态机包含子状态机的多状态机结构,里面包含了许多与面向对象相似的思想, 所以它的功能也要比有限状态机更加强大, 当一个问题用有限状态机解决起来有些吃力的时候, 就需要层次状态机出马了。 层次状态机理论我理解得也不透彻, 就不在这里班门弄斧了,大家可以找一些有关状态机理论的专业书籍来读一读。要掌握状态机编程,理解状态机(主要指有限状态机)只是第一步,也是最简单的一步,更重要的技能是能用状态机这个工具去分析解剖实际问题:划分状态、 提取事件、 确定转换关系、规定动作等等,形成一张完整的状态转换图,最后还要对转换图进行优化,达到最佳。 把实际问题变成了状态转换图, 工作的一大半就算完成了, 这个是具有架构师气质的任务,剩下的问题就是按照状态图编程写代码了,这个是具有代码工特色的工作。 本文来源网络,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。 往期推荐:往期推荐 咱们是时候改变一下嵌入式软件开发思维方式了! 什么是不完全类型? C语言、嵌入式中几个非常实用的宏技巧 干货 | 浅析程序开机自启动

    嵌入式大杂烩 状态机

  • 电阻的分类有哪些?电阻值怎么看?

    什么是电阻?电阻是一种被动的电子元器件,能够阻碍电子电流的流动。你见过被点亮的LED吗?有了电阻,它才能明亮的发光而不是被烧毁。电阻的取值单位是Ohm(Ω),在电路中符合欧姆定律。电阻的国际符号是一个矩形方块,但美国的标准符号是一个波浪线。电阻的类型有哪些?从结构上分有固定电阻和可变电阻。固定电阻的基本参数是阻值和公差。公差是指由于温度和光照变化而引起的变化。可变电阻包括可变电阻器和物理量电阻传感器。可变电阻器包括可调电位器和滑动变阻器。物理量电阻传感器包括热敏传感器,光敏传感器,压敏传感器和磁敏传感器。从制造材料上分,包括碳电阻,碳膜电阻,金属膜电阻,厚薄膜电阻,箔电阻,绕线电阻。碳电阻是比较老的结构。精度较低,通常用于产生高能脉冲的地方。绕线电阻是最老的一种结构。阻值精确,通常用于高功率应用。小阻值仍旧很可靠。现在,使用较多的是金属和金属氧化物电阻器。阻值和公差较稳定,温度系数也比较好。如何使用电阻?通常电阻用于分压,产生热量,阻抗匹配,在电路中作为负载,增益控制,延时,甚至作为火车中的电动控制器的能量泄放电阻。典型应用为:根据欧姆定律测量电路电流;驱动LED;在汽车通风系统中,驱动鼓风机电机。电阻值的表示?通常有两种方法,一种是色标法,一种是数值标记法(SMD Codes)。色环电阻通常有3至6条色环。通常使用较多的是4色环和5色环。前两条色环代表电阻值的阻值,第三条通常代表指数因子,第四条表示阻值公差。下面是数值标记法(SMD Codes)的举例。END来源:网络版权归原作者所有,如有侵权,请联系删除。▍

    嵌入式ARM 电阻值

  • 51单片机的启动代码里面有什么?

    在我们使用kei c51创建一个51单片机项目时,会有如下图所示的提示:keil创建新项目时,提示是否添加启动文件一般情况下,需要选择“是”。当然,也可以选择不加。那么,这个启动文件的作用是什么?什么情况下需要加,什么情况下可以不加?今天我们就来详细了解一下这个启动文件的内容,看明白这个内容后,我们就会有种恍然大悟的感觉:“哦,原来是这样啊!”启动代码第一段▼以下是启动代码原文第一段:$NOMOD51;------------------------------------------------------------------------------; This file is part of the C51 Compiler package; Copyright (c) 1988-2005 Keil Elektronik GmbH and Keil Software, Inc.; Version 8.01;; *** > ***;------------------------------------------------------------------------------; STARTUP.A51: This code is executed after processor reset.;; To translate this file use A51 with the following invocation:;; A51 STARTUP.A51;; To link the modified STARTUP.OBJ file to your application use the following; Lx51 invocation:;; Lx51 your object file list, STARTUP.OBJ controls;;------------------------------------------------------------------------------;; User-defined Power-On Initialization of Memory;; With the following EQU statements the initialization of memory; at processor reset can be defined:;; IDATALEN: IDATA memory size ; Note: The absolute start-address of IDATA memory is always 0; The IDATA space overlaps physically the DATA and BIT areas.IDATALEN EQU 80H;; XDATASTART: XDATA memory start address ; The absolute start address of XDATA memoryXDATASTART EQU 0 ;; XDATALEN: XDATA memory size ; The length of XDATA memory in bytes.XDATALEN EQU 0 ;; PDATASTART: PDATA memory start address ; The absolute start address of PDATA memoryPDATASTART EQU 0H;; PDATALEN: PDATA memory size ; The length of PDATA memory in bytes.PDATALEN EQU 0H;;▼以下是启动代码第一段的翻译:不使用预先定义的SFR。就是告诉汇编器不使用预定义的寄存器名,因为汇编器内部定义了51的寄存器名,但在实际使用时会用51的扩展芯片例如52之类的,如果包含了52的头文件就会出现重复定义所以要先声明一下不适用汇编器内部定义的寄存器名。这个文件是C51编译器包的一部分版权所有(c) 1988-2005 Keil Elektronik GmbH和Keil Software, Inc。版本8.01*** > ***----------------------------------------------------STARTUP.A51里面的代码在处理器复位后执行。用下面的命令行语句调用A51进行编译产生目标文件,A51 STARTUP.A51用下面的命令行语句调用BL51连接器把STARTUP.OBJ目标文件连接到程序代码中,Lx51 invocation:Lx51调用---------------------------------------------------Lx51 调用目标文件列表, 由STARTUP.OBJ 目标文件控制用户自定义上电后需要初始化的储存区域(初始化RAM区的数据)在处理器复位时通过下列EQU伪指令来初始化内存(RAM单元)IDATALEN:IDATA存储区的大小,可以根据自己的选择修改IDATA绝对的起始地址总是0IDATA区涵盖DATA和BIT区(DATA区(直接寻址区)以及 BIT区 (位寻址区)),;至少要保证与C51编译器运行库有关的存储器的空间进行0初始化XDATA存储区的起始地址XDATA内存的绝对起始地址。XDATA存储器空间的绝对起始地址为0,XDATA空间的大小XDATA空间的长度以字节为单位说明xdata的字节数清0,该值默认为0PDATA空间的大小PDATA存储器的空间的绝对起始地址需用0进行初始化的PDATA存储器的空间字节数在51系列中data、idata、xdata、pdata的区别:data:固定指前面0x00-0x7f的128个RAM。idata:固定指前面0x00-0xff的256个RAM,其中前128和data的128完全相同,只是因为访问的方式不同。xdata:外部扩展RAM,一般指外部0x0000-0xffff空间。pdata:外部扩展RAM的低256个字节。需用0进行初始化的IDATA存储器空间的字节数,IDATALEN只是一个标号(与IDATA不一样哦),EQU只是做宏一样的替换,类似于C语言中的#define uint (unsigned int),以上的代码使得程序以后在碰到IDATALEN时替换成80H。IDATALEN可以定义为你自己喜欢的名字如MyDataLen等。之所以用IDATALEN,一是为了好记,二是为了表明和IDATA有关。各种常数名及其含义启动代码第二段我们继续来看看51单片机的启动代码里面都有哪些东西。▼下面先列出51单片机启动代码第二部分的原文:;------------------------------------------------------------------------------;; Reentrant Stack Initialization;; The following EQU statements define the stack pointer for reentrant; functions and initialized it:;; Stack Space for reentrant functions in the SMALL model.; IBPSTACK: Enable SMALL model reentrant stack; Stack space for reentrant functions in the SMALL model.IBPSTACK EQU 0 ; set to 1 if small reentrant is used.; IBPSTACKTOP: End address of SMALL model stack ; Set the top of the stack to the highest location.IBPSTACKTOP EQU 0xFF 1 ; default 0FFH 1 ; ;; Stack Space for reentrant functions in the LARGE model. ; XBPSTACK: Enable LARGE model reentrant stack; Stack space for reentrant functions in the LARGE model.XBPSTACK EQU 0 ; set to 1 if large reentrant is used.; XBPSTACKTOP: End address of LARGE model stack ; Set the top of the stack to the highest location.XBPSTACKTOP EQU 0xFFFF 1 ; default 0FFFFH 1 ; ;; Stack Space for reentrant functions in the COMPACT model. ; PBPSTACK: Enable COMPACT model reentrant stack; Stack space for reentrant functions in the COMPACT model.PBPSTACK EQU 0 ; set to 1 if compact reentrant is used.;; PBPSTACKTOP: End address of COMPACT model stack ; Set the top of the stack to the highest location.PBPSTACKTOP EQU 0xFF 1 ; default 0FFH 1 ; ;原文全是伪指令、宏定义这些东东,看起来确实很头疼啊。我们简单的翻译一下吧。▼以下是第二段启动代码翻译:再入函数模拟初始化;以下用EQU指令定义了再入函数模拟堆栈指针的初始化;使用SMALL存储器模式时再入函数的堆栈空间;IBPSTACK EQU 0 ; 使用SMALL存储器模式再入函数时将其设置成1;IBPSTACKTOP EQU 0FFH 1 ; 将堆栈顶设置为最高地址 1;使用LARGE存储器模式时再入函数的堆栈空间;XBPSTACK EQU 0 ; 使用LARGE存储器模式再入函数时将其设置成1;XBPSTACKTOP EQU 0FFFFH 1; 将堆栈顶设置为最高地址 1;使用COMPACT存储器模式时再入函数的堆栈空间;PBPSTACK EQU 0 ; 使用COMPACT存储器模式再入函数时将其设置成1;PBPSTACKTOP EQU 0FFFFH 1; 将堆栈顶设置为最高地址 1。▼三种模式解析这里提到了SMALL,LARGE,COMPACT三种模式。这三种模式究竟有什么含义呢?我们下面就来了解一下。不同内存模式下的堆栈。Keil 编译器中有三种模式设置。这是由51处理器繁多的寻址模式导致的,不同的寻址模式有不同的效率。small模式:在small模式中,所有默认变量均装入单片机内部的RAM中,51单片机默认内部RAM只有128B;52单片机默认256B。该模式下的优点是访问速度快,缺点是空间有限。compact模式:在compact模式中,所有默认变量均位于单片机的256B RAM中,和在small模式中使用关键字 pdata来定义数据变量的效果一样,在该模式下程序总变量空间不能超过256B。large模式:在large模式中,所有默认变量可放在多达64KB的RAM中,包括内部RAM和外部RAM,这和使用关键字xdata 来定义变量的效果一样。small:变量存储在内部ram里。compact:变量存储在外部ram里,使用页8位间接寻址。large:变量存储在外部Ram里,使用16位间接寻址。我们一般使用small来存储变量,就是说单片机优先把变量存储在内部ram里,如果内部ram不够了,才会存到外部去。compact的方式要自己通过程序来指定页的高位地址,编程比较复杂,如果外部ram很少,只有256个字节,那么对该256个字节的读取就比较快。如果超过256字节,那么要不断地进行切换的话,就比较麻烦。compact模式适用于比较少的外部ram的情况。large模式,是指变量会优先分配到外部ram里。3种存储方式都支持内部256字节和外部64k字节的ram。区别是变量的优先(或默认)存储在哪里的区别。除非你不想把变量存储在内部ram,才使用后面的compact、large模式。因为变量存储在内部ram里,运算速度比存储在外部ram要快的多,大部分的应用都是选择Small的模式。END来源:老马识途单片机版权归原作者所有,如有侵权,请联系删除。▍

    嵌入式ARM 51单片机 启动代码

  • 一步到位!教你RT-Thread上设备IIC驱动移植

    趟过前面RT-Thread在GD32E230CotexM23上的坑之后,继续进行了RT-Thread端设备驱动的验证测试。《国产GD32替代:RT-Thread在CotexM23上的起起伏伏》IIC作为很多设备之间通信的基础通信方式,起使用程度和普及程度基本也算是很多MCU的标配,即使没有硬件IIC,用软件对GPIO口也可以模拟出软件方面的IIC,自然GD32E230作为ARMv8架构的CotexM23系列的芯片,硬件IIC还是有的,而且SDK里面也留了完整的例程,可以根据实际需求去使用。当然本文不是想使用裸机上的IIC驱动,而是RT-Thread RTOS上的IIC驱动,这里我们还是先来看看RT-Thread上外设驱动的结构,参考的官方文档。其他的细节还是参考官方文档,以下是官方链接:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/device1、RT-Thread移植IIC到CotexM23中工程文件芯片软件IIC、硬件IIC选取芯片硬件端使用函数RT-Thread端驱动层IIC数据传输里面比较关键的函数处理好上面的相关内容后,还有一些宏的使用,以及硬件IIC使用的是那些IO口,然后跟裸机一样的情形,需要回到对硬件的一些初始化操作。要在RTThread上用上驱动框架的东西,实际上就是要中间使用一个过渡层,原来操作硬件使用直接操作寄存器,RT-Thread上处理直接操作寄存器,还需要对相应的读写等函数进行封装,供应用层直接使用,所以实际上较裸机程序多了很多内容,但是这个好处也是有的,可以将系统和硬件进行隔离。移植完上面开始进行EEPROM的操作实践。2、RT-Thread IIC驱动EEPROM多的不用说,看写EEPROM寄存器接下来看看读寄存器读写EEPROM内容这里有一些细节说明,根据IIC的操作时序,给寄存器写值之前,首先需要写入寄存器的地址,因此msg.buf 中需要包含寄存器的地址,和后续写入的值,如要写入EEPROM前面0-15寄存器的值,定义的msg.buf的长度为17个字节,读的过程直接将写地址和读寄存器分开写,这样更清晰,主要是根据IIC的操作时序,多字节读写等相关内容,具体的也可以百度查找。3、RT-Thread IIC驱动EEPROM实践2同样的首先看读写寄存器函数读写EEPROM内容运行结果如下图这两者之间只是函数使用的不同,然后操作的细节可能不一样,但是还是前面一种更符合RT-Thread系统上使用的IIC驱动。4、RT-Thread IIC总结完成以上的移植和EEPROM上面的实践,下一步运用到实际项目中IIC驱动气压芯片,由于芯片、传感器紧张,打算使用多种气压传感器,以作备胎,呵呵!文章纯属个人意见,如有不妥之处请指正,谢谢!文章最后附上此次移植、实践工程代码。  GD32E23x_Demo_RTT_IIC.rar (1.5 MB, 附件下载请点击“阅读原文”)END来源:网络版权归原作者所有,如有侵权,请联系删除。▍

    嵌入式ARM 移植 RT-Thread IIC驱动

  • 一文看懂:单电源运放和双电源运放有啥区别?

    在设计单电源电路时需要比双电源电路更加小心,设计者必须要完全理解这篇文章中所述的内容。11.1电源供电和单电源供电所有的运算放大器都有两个电源引脚,一般在资料中,它们的标识是VCC+和VCC-,但是有些时候它们的标识是VCC+和GND。这是因为有些数据手册的作者企图将这种标识的差异作为单电源运放和双电源运放的区别。但是,这并不是说他们就一定要那样使用――他们可能可以工作在其他的电压下。在运放不是按默认电压供电的时候,需要参考运放的数据手册,特别是绝对最大供电电压和电压摆动说明。绝大多数的模拟电路设计者都知道怎么在双电源电压的条件下使用运算放大器,比如图一左边的那个电路,一个双电源是由一个正电源和一个相等电压的负电源组成。一般是正负15V,正负12V和正负5V也是经常使用的。输入电压和输出电压都是参考地给出的,还包括正负电压的摆动幅度极限Vom以及最大输出摆幅。单电源供电的电路(图一中右)运放的电源脚连接到正电源和地。正电源引脚接到VCC+,地或者VCC-引脚连接到GND。将正电压分成一半后的电压作为虚地接到运放的输入引脚上,这时运放的输出电压也是该虚地电压,运放的输出电压以虚地为中心,摆幅在Vom 之内。有一些新的运放有两个不同的最高输出电压和最低输出电压。这种运放的数据手册中会特别分别指明Voh和Vol 。需要特别注意的是有不少的设计者会很随意的用虚地来参考输入电压和输出电压,但在大部分应用中,输入和输出是参考电源地的,所以设计者必须在输入和输出的地方加入隔直电容,用来隔离虚地和地之间的直流电压。(参见1.3节)通常单电源供电的电压一般是5V,这时运放的输出电压摆幅会更低。另外现在运放的供电电压也可以是3V也或者会更低。出于这个原因在单电源供电的电路中使用的运放基本上都是Rail-To-Rail的运放,这样就消除了丢失的动态范围。需要特别指出的是输入和输出不一定都能够承受Rail-To-Rail的电压。虽然器件被指明是轨至轨(Rail-To-Rail)的,如果运放的输出或者输入不支持轨至轨,接近输入或者接近输出电压极限的电压可能会使运放的功能退化,所以需要仔细的参考数据手册是否输入和输出是否都是轨至轨。这样才能保证系统的功能不会退化,这是设计者的义务。1.2虚 地单电源工作的运放需要外部提供一个虚地,通常情况下,这个电压是VCC/2,图二的电路可以用来产生VCC/2的电压,但是他会降低系统的低频特性。R1和R2是等值的,通过电源允许的消耗和允许的噪声来选择,电容C1是一个低通滤波器,用来减少从电源上传来的噪声。在有些应用中可以忽略缓冲运放。在下文中,有一些电路的虚地必须要由两个电阻产生,但是其实这并不是完美的方法。在这些例子中,电阻值都大于100K,当这种情况发生时,电路图中均有注明。1.3交流耦合虚地是大于电源地的直流电平,这是一个小的、局部的地电平,这样就产生了一个电势问题:输入和输出电压一般都是参考电源地的,如果直接将信号源的输出接到运放的输入端,这将会产生不可接受的直流偏移。如果发生这样的事情,运放将不能正确的响应输入电压,因为这将使信号超出运放允许的输入或者输出范围。解决这个问题的方法将信号源和运放之间用交流耦合。使用这种方法,输入和输出器件就都可以参考系统地,并且运放电路可以参考虚地。当不止一个运放被使用时,如果碰到以下条件级间的耦合电容就不是一定要使用:第一级运放的参考地是虚地第二级运放的参考地也是虚地这两级运放的每一级都没有增益。任何直流偏置在任何一级中都将被乘以增益,并且可能使得电路超出它的正常工作电压范围。如果有任何疑问,装配一台有耦合电容的原型,然后每次取走其中的一个,观察电工作是否正常。除非输入和输出都是参考虚地的,否则这里就必须要有耦合电容来隔离信号源和运放输入以及运放输出和负载。一个好的解决办法是断开输入和输出,然后在所有运放的两个输入脚和运放的输出脚上检查直流电压。所有的电压都必须非常接近虚地的电压,如果不是,前级的输出就就必须要用电容做隔离。(或者电路有问题)1.4组合运放电路在一些应用中,组合运放可以用来节省成本和板上的空间,但是不可避免的引起相互之间的耦合,可以影响到滤波、直流偏置、噪声和其他电路特性。设计者通常从独立的功能原型开始设计,比如放大、直流偏置、滤波等等。在对每个单元模块进行校验后将他们联合起来。除非特别说明,否则本文中的所有滤波器单元的增益都是 1。1.5选择电阻和电容的值每一个刚开始做模拟设计的人都想知道如何选择元件的参数。电阻是应该用1欧的还是应该用1兆欧的?一般的来说普通的应用中阻值在K欧级到100K欧级是比较合适的。高速的应用中阻值在100欧级到1K欧级,但他们会增大电源的消耗。便携设计中阻值在1兆级到10兆欧级,但是他们将增大系统的噪声。用来选择调整电路参数的电阻电容值的基本方程在每张图中都已经给出。如果做滤波器,电阻的精度要选择1% E-96系列。一但电阻值的数量级确定了,选择标准的E-12系列电容。用E-24系列电容用来做参数的调整,但是应该尽量不用。用来做电路参数调整的电容不应该用5%的,应该用1%。22.1放 大放大电路有两个基本类型:同相放大器和反相放大器。他们的交流耦合版本如图三所示。对于交流电路,反向的意思是相角被移动180度。这种电路采用了耦合电容Cin。Cin被用来阻止电路产生直流放大,这样电路就只会对交流产生放大作用。如果在直流电路中,Cin被省略,那么就必须对直流放大进行计算。在高频电路中,不要违反运放的带宽限制,这是非常重要的。实际应用中,一级放大电路的增益通常是100倍(40dB),再高的放大倍数将引起电路的振荡,除非在布板的时候就非常注意。如果要得到一个放大倍数比较的大放大器,用两个等增益的运放或者多个等增益运放比用一个运放的效果要好的多。2.2衰 减传统的用运算放大器组成的反相衰减器如图四所示。在电路中R2要小于R1。这种方法是不被推荐的,因为很多运放是不适宜工作在放大倍数小于1倍的情况下。正确的方法是用图五的电路。在表一中的一套规格化的R3的阻值可以用作产生不同等级的衰减。对于表中没有的阻值,可以用以下的公式计算R3=(Vo/Vin)/(2-2(Vo/Vin))如果表中有值,按以下方法处理:为Rf和Rin在1K到100K之间选择一个值,该值作为基础值。将Rin 除以二得到RinA 和RinB。将基础值分别乘以1或者2就得到了Rf、Rin1和Rin2,如图五中所示。在表中给R3选择一个合适的比例因子,然后将他乘以基础值。比如,如果Rf是20K,RinA和RinB都是10K,那么用12.1K的电阻就可以得到-3dB的衰减。图六中同相的衰减器可以用作电压衰减和同相缓冲器使用。2.3加法器图七是一个反相加法器,他是一个基本的音频混合器。但是该电路的很少用于真正的音频混合器。因为这会逼近运放的工作极限,实际上我们推荐用提高电源电压的办法来提高动态范围。同相加法器是可以实现的,但是是不被推荐的。因为信号源的阻抗将会影响电路的增益。2.4减法器就像加法器一样,图八是一个减法器。一个通常的应用就是用于去除立体声磁带中的原唱而留下伴音(在录制时两通道中的原唱电平是一样的,但是伴音是略有不同的)。2.5模拟电感图九的电路是一个对电容进行反向操作的电路,它用来模拟电感。电感会抵制电流的变化,所以当一个直流电平加到电感上时电流的上升是一个缓慢的过程,并且电感中电阻上的压降就显得尤为重要。电感会更加容易的让低频通过它,它的特性正好和电容相反,一个理想的电感是没有电阻的,它可以让直流电没有任何限制的通过,对频率是无穷大的信号有无穷大的阻抗。如果直流电压突然通过电阻R1加到运放的反相输入端上的时候,运放的输出将不会有任何的变化,因为这个电压通过电容C1也同样加到了正相输出端上,运放的输出端表现出了很高的阻抗,就像一个真正的电感一样。随着电容C1不断的通过电阻R2进行充电,R2上电压不断下降,运放通过电阻R1汲取电流。随着电容不断的充电,最后运放的两个输入脚和输出脚上的电压最终趋向于虚地(Vcc/2)。当电容C1完全被充满时,电阻R1限制了流过的电流,这就表现出一个串连在电感中电阻。这个串连的电阻就限制了电感的Q值。真正电感的直流电阻一般会比模拟的电感小的多。这有一些模拟电感的限制:电感的一段连接在虚地上;模拟电感的Q值无法做的很高,取决于串连的电阻R1;模拟电感并不像真正的电感一样可以储存能量,真正的电感由于磁场的作用可以引起很高的反相尖峰电压,但是模拟电感的电压受限于运放输出电压的摆幅,所以响应的脉冲受限于电压的摆幅。2.6仪用放大器仪用放大器用于需要对小电平信号直流信号进行放大的场合,它是由减法器拓扑而来的。仪用放大器利用了同相输入端高阻抗的优势。基本的仪用放大器如图十所示。这个电路是基本的仪用放大电路,其他的仪用放大器也如图中所示,这里的输入端也使用了单电源供电。这个电路实际上是一个单电源的应变仪。这个电路的缺点是需要完全相等的电阻,否则这个电路的共模抑制比将会很低。图十中的电路可以简单的去掉三个电阻,就像图十一中的电路。这个电路的增益非常好计算。但是这个电路也有一个缺点:那就是电路中的两个电阻必须一起更换,而且他们必须是等值的。另外还有一个缺点,第一级的运放没有产生任何有用的增益。另外用两个运放也可以组成仪用放大器,就像图十二所示。但是这个仪用放大器是不被推荐的,因为第一个运放的放大倍数小于一,所以他可能是不稳定的,而且Vin-上的信号要花费比Vin+上的信号更多的时间才能到达输出端。这节非常深入地介绍了用运放组成的有源滤波器。在很多情况中,为了阻挡由于虚地引起的直流电平,在运放的输入端串入了电容。这个电容实际上是一个高通滤波器,在某种意义上说,像这样的单电源运放电路都有这样的电容。设计者必须确定这个电容的容量必须要比电路中的其他电容器的容量大100倍以上。这样才可以保证电路的幅频特性不会受到这个输入电容的影响。如果这个滤波器同时还有放大作用,这个电容的容量最好是电路中其他电容容量的1000倍以上。如果输入的信号早就包含了VCC/2的直流偏置,这个电容就可以省略。这些电路的输出都包含了VCC/2的直流偏置,如果电路是最后一级,那么就必须串入输出电容。这里有一个有关滤波器设计的协定,这里的滤波器均采用单电源供电的运放组成。滤波器的实现很简单,但是以下几点设计者必须注意:1)滤波器的拐点(中心)频率2)滤波器电路的增益3)带通滤波器和带阻滤波器的的Q值4)低通和高通滤波器的类型(Butterworth 、Chebyshev、Bessell)不幸的是要得到一个完全理想的滤波器是无法用一个运放组成的。即使可能,由于各个元件之间的复杂互感而导致设计者要用非常复杂的计算才能完成滤波器的设计。通常对波形的控制要求越复杂就意味着需要更多的运放,这将根据设计者可以接受的最大畸变来决定。或者可以通过几次实验而最终确定下来。如果设计者希望用最少的元件来实现滤波器,那么就别无选择,只能使用传统的滤波器,通过计算就可以得到了。33.1一阶滤波器一阶滤波器是最简单的电路,他们有20dB每倍频的幅频特性3.1.1 低通滤波器典型的低通滤波器如图十三所示。3.1.2 高通滤波器典型的高通滤波器如图十四所示。3.1.3 文氏滤波器文氏滤波器对所有的频率都有相同的增益,但是它可以改变信号的相角,同时也用来做相角修正电路。图十五中的电路对频率是F的信号有90度的相移,对直流的相移是0度,对高频的相移是180度。3.2二阶滤波器二阶滤波电路一般用他们的发明者命名。他们中的少数几个至今还在使用。有一些二阶滤波器的拓扑结构可以组成低通、高通、带通、带阻滤波器,有些则不行。这里没有列出所有的滤波器拓扑结构,只是将那些容易实现和便于调整的列了出来。二阶滤波器有40dB每倍频的幅频特性。通常的同一个拓扑结构组成的带通和带阻滤波器使用相同的元件来调整他们的Q值,而且他们使滤波器在Butterworth和Chebyshev滤波器之间变化。必须要知道只有Butterworth滤波器可以准确的计算出拐点频率,Chebyshev和Bessell滤波器只能在Butterworth滤波器的基础上做一些微调。我们通常用的带通和带阻滤波器有非常高的Q值。如果需要实现一个很宽的带通或者带阻滤波器就需要用高通滤波器和低通滤波器串连起来。对于带通滤波器的通过特性将是这两个滤波器的交叠部分,对于带阻滤波器的通过特性将是这两个滤波器的不重叠部分。这里没有介绍反相Chebyshev和Elliptic滤波器,因为他们已经不属于电路集需要介绍的范围了。不是所有的滤波器都可以产生我们所设想的结果――比如说滤波器在阻带的最后衰减幅度在多反馈滤波器中的会比在Sallen-Key滤波器中的大。由于这些特性超出了电路图集的介绍范围,请大家到教科书上去寻找每种电路各自的优缺点。不过这里介绍的电路在不是很特殊的情况下使用,其结果都是可以接受的。3.2.1 Sallen-Key滤波器Sallen-Key滤波器是一种流行的、广泛应用的二阶滤波器。他的成本很低,仅需要一个运放和四个无源器件组成。但是换成Butterworth或Chebyshev滤波器就不可能这么容易的调整了。这个电路是一个单位增益的电路,改变Sallen-Key滤波器的增益同时就改变了滤波器的幅频特性和类型。实际上Sallen-Key滤波器就是增益为1的Butterworth滤波器。3.2.2 多反馈滤波器多反馈滤波器是一种通用,低成本以及容易实现的滤波器。不幸的是,设计时的计算有些复杂,在这里不作深入的介绍。如果需要的是一个单位增益的Butterworth滤波器,那么这里的电路就可以给出一个近似的结果。3.2.3 双T滤波器双T滤波器既可以用一个运放也可仪用两个运放实现。他是建立在三个电阻和三个电容组成的无源网络上的。这六个元件的匹配是临界的,但幸运的是这仍是一个常容易的过程,这个网络可以用同一值的电阻和同一值的电容组成。用图中的公式就可以同时的将R3和C3计算出来。应该尽量选用同一批的元件,他们有非常相近的特性。3.2.3.1 单运放实现如果用参数非常接近的元件组成带通滤波器,就很容易发生振荡。接到虚地的电阻最好在E-96 1%系列中选择,这样就可以破坏振荡条件。3.2.2.2 双运放实现典型的双运放如图20到图22所示END来源:网络版权归原作者所有,如有侵权,请联系删除。▍

    嵌入式ARM 运放 单电源 双电源

  • 嵌入式开发中,为何大部分通信总线都差不多?

    在进行嵌入式开发中你一定遇到了各种总线,包括芯片内的地址总线和数据总线,高速总线和低速总线,还有芯片外的各种通信总线等等。而往往大家对总线似乎还是把握得不太好,所以今天挑选了一篇关于总线的文章供大家阅读。如果一座只能容一个人来往的独木桥,两端的人都想要过桥,为了不拥挤、阻塞,那我们就得采取有效的办法。比如规定某段时间哪端的人过桥,另一端的人就等着该他过桥的时间段的到来,同时也还可以规定人多时要按先来后到或年龄长幼的次序过桥。在这不经意间,我们就体会到了现代电子信息数据通过总线按时分系统传输的最原始的思想。现代网络信息的发展,特别是对于成本和空间而言,总线传输替代点对点传输是目前发展的热点,它的出现将给信息传输上提供了最大的方便和最有效的技术解决方案。假如一个微处理器与它的部件和外围设备都分别用点对点的线路来连接通讯,则所有连线将会错综复杂,甚至难以实现。目前与我们生活习习相关的一系列活动都无不牵涉到总线技术的应用,如我们上英特网、给亲戚朋友打电话、用U盘来存储信息等。虽然流行的总线所采取的形式不同,但他们主要的原则性思想无非就是时分系统、频分系统、相分系统和码分系统等。常言道“兵来将挡,水来土淹”,面对种类繁多的总线,我们只有从基本原理出发,从骨子里去了解它的实质,而不要被它形式多样的外表所迷惑,才能熟练掌握和灵活运用眼下正在或将要用到的各种总线技术。总线的定义总线,英文叫作“BUS”,即我们中文的“公共车”,这是非常形象的比如,公共车走的路线是一定的,我们任何人都可以坐公共车去该条公共车路线的任意一个站点。如果把我们人比作是电子信号,这就是为什么英文叫它为“BUS”而不是“CAR”的真正用意。当然,从专业上来说,总线是一种描述电子信号传输线路的结构形式,是一类信号线的集合,是子系统间传输信息的公共通道。通过总线能使整个系统内各部件之间的信息进行传输、交换、共享和逻辑控制等功能。如在计算机系统中,它是CPU、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过主机相连接,外部设备通过相应的接口电路再与总线相连接。总线的分类总线分类的方式有很多,如被分为外部和内部总线、系统总线和非系统总线等等,下面是几种最常用的分类方法。1 按功能分类最常见的是从功能上来对数据总线进行划分,可以分为地址总线(address bus)、数据总线(data bus)和控制总线(control bus)。在有的系统中,数据总线和地址总线可以在地址锁存器控制下被共享,也即复用。地址总线是专门用来传送地址的。在设计过程中,见得最多的应该是从CPU地址总线来选用外部存储器的存储地址。地址总线的位数往往决定了存储器存储空间的大小,比如地址总线为16位,则其最大可存储空间为216(64KB)。数据总线是用于传送数据信息,它又有单向传输和双向传输数据总线之分,双向传输数据总线通常采用双向三态形式的总线。数据总线的位数通常与微处理的字长相一致。例如Intel 8086微处理器字长16位,其数据总线宽度也是16位。在实际工作中,数据总线上传送的并不一定是完全意义上的数据。控制总线是用于传送控制信号和时序信号。如有时微处理器对外部存储器进行操作时要先通过控制总线发出读/写信号、片选信号和读入中断响应信号等。控制总线一般是双向的,其传送方向由具体控制信号而定,其位数也要根据系统的实际控制需要而定。2 按传输方式分类按照数据传输的方式划分,总线可以被分为串行总线和并行总线。从原理来看,并行传输方式其实优于串行传输方式,但其成本上会有所增加。通俗地讲,并行传输的通路犹如一条多车道公路,而串行传输则是只允许一辆汽车通过单线公路。目前常见的串行总线有SPI、I2C、USB、IEEE1394、RS232、CAN等;而并行总线相对来说种类要少,常见的如IEEE1284、ISA、PCI等。3 按时钟信号方式分类按照时钟信号是否独立,可以分为同步总线和异步总线。同步总线的时钟信号独立于数据,也就是说要用一根单独的线来作为时钟信号线;而异步总线的时钟信号是从数据中提取出来的,通常利用数据信号的边沿来作为时钟同步信号。总线传输基本原理依据前面对总线的定义可知总线的基本作用就是用来传输信号,为了各子系统的信息能有效及时的被传送,为了不至于彼此间的信号相互干扰和避免物理空间上过于拥挤,其最好的办法就是采用多路复用技术,也就是说总线传输的基本原理就是多路复用技术。所谓多路复用,就是指多个用户共享公用信道的一种机制,目前最常见的主要有时分多路复用、频分多路复用和码分多路复用等。1 时分多路复用(TDMA)时分复用是将信道按时间加以分割成多个时间段,不同来源的信号会要求在不同的时间段内得到响应,彼此信号的传输时间在时间坐标轴上是不会重叠。2 频分多路复用(FDMA)频分复用就是把信道的可用频带划分成若干互不交叠的频段,每路信号经过频率调制后的频谱占用其中的一个频段,以此来实现多路不同频率的信号在同一信道中传输。而当接收端接收到信号后将采用适当的带通滤波器和频率解调器等来恢复原来的信号。3 码分多路复用(CDMA)码分多路复用时所被传输的信号都会有各自特定的标识码或地址码,接收端将会根据不同的标识码或地址码来区分公共信道上的传输信息,只有标识码或地址码完全一致的情况下传输信息才会被接收。总线的通信协议对于总线的学习,了解其通信协议是整个过程中最关键的一步,所有介绍总线技术的资料都会花很大的篇幅来描述其协议,特别是ISO/OSI的那七层定义。其实,要了解一种总线的协议,最主要的就是去了解总线的帧数据每一位所代表的特性和意义,总线各节点间有效数据的收发都是通过各节点对帧数据位或段的判断和确信来得以实现。如图1所示是常见的I2C总线上传输的一字节数据的数据帧,其总线形式是由数据线SDA和时钟SCL构成的双线制串行总线,并接在总线上的电路模块即可作为发送器(主机)又可作为接收器(从机)。帧数据中除了控制码(包括从机标识码和访问地址码)与数据码外还包括起始信号、结束信号和应答信号。起始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。控制码:用来选择操作目标与对象,即接通需要控制的电路,确定控制的种类对象。在读期间,也即SCL时钟线处于时钟脉冲高电平时,SDA上的数据位不会跳变。数据码:是主机向从机发送的具体的有用的数据(如对比度、亮度等)和信息。在读期间,SDA上的数据位不会跳变。应答信号:接收方收到8bit数据后,向发送方发出特定的低电平。读/写的方向与其它数据位正好相反,也即是由从机写出该低电平,主机来读取该低电平。结束信号:SCL为高电平时,SDA由低电平向高电平跳变表示数据帧传输结束。当然不同的总线其数据位或段的定义肯定不同,但依据同样的原理可以更快的去了解它的协议的特性和特点。虽然其信息帧的大小不一,但具体的某一数据位或数据段都类似于本文所提及的I2C总线,会依据它的协议的要求来定义它所达标的意义和功能。主要技术指标评价总线的主要技术指标是总线的带宽(即传输速率)、数据位的宽度(位宽)、工作频率和传输数据的可靠性、稳定性等。1 带宽(传输速率)、位宽和工作频率总线的带宽指的是单位时间内总线上传送的数据量,即每秒传送MB的最大数据传输率。总线的位宽指的是总线能同时传送的二进制数据的位数,或数据总线的位数,即32位、64位等总线宽度的概念;总线的位宽越宽,数据传输速率越大,总线的带宽就越宽。总线的工作时钟频率以MHz为单位,它与传输的介质、信号的幅度大小和传输距离有关。在同样硬件条件下,我们采用差分信号传输时的频率常常会比单边信号高得多,这是因为差分信号的的幅度只有单边信号的一半而已。总线的带宽、位宽和工作频率,这三者密切相关,它们之间的关系:2 传输数据的可靠性可靠性是评定总线最关键的参数,没有可靠性,传输的数据都是错误的信息,便就失去了总线的实际意义。为了提高总线的可靠性,通常采用的措施有:1)采用数据帧发送前发送器对总线进行侦听,只有侦听到总线处于空闲状态下时才可向总线传送数据帧,这样避免了不同节点的数据冲突。2)采用双绞线差分信号来传送数据,以降低单线的电压升降幅度,减小信号的边沿产生的高次谐波。3)适当的让数据的边沿具有一定的斜坡。4)增加匹配电阻和电容等来减少总线上信号的发射和平衡总线上的分布电容等。5)采用合适的网络拓扑结构和屏蔽技术等来减少受其他信号的干扰。还有就是在软件上通过数字滤波、数据校验纠错等措施来提高数据传输的可靠性。结束语学习是一个循序渐进的过程,对总线技术的学习和理解也是随着其技术的不断发展而不断更新的过程。子曰“工欲善其事,必先利其器。”只有从最基本的原理出发,打好基础,才能在今后的学习中融会贯通,前仆后继,更进一步深入该知识点和拓宽知识面。END来源:网络版权归原作者所有,如有侵权,请联系删除。▍

    嵌入式ARM 嵌入式开发 通信总线

  • 全网首发!RT-Thread移植TouchGFX从零开始详细教程!

    1.介绍TouchGFX是一个图形框架,它可以帮助用户基于STM32创建出类似于当前智能手机风格的用户界面,对于STM32的用户,它是免费的!RT-Thread 是一款完全由国内团队开发维护的嵌入式实时操作系统(RTOS),具有完全的自主知识产权。现在我们就把TouchGFX和RT-Thread融合起来,保姆级教程走起!2.开发环境准备源码:RT-Thread 4.0.2;工具:Env 1.2.0;软件:Keil MDK 5.30;TouchGFX Designer 4.16.0;STM32CubeMX 6.2.0;开发板:正点原子阿波罗F429;正点原子800x480屏幕;开发系统:Windows10;是不是觉得要准备的软件和工具有点多,不过只要开发过【RT-Thread】的话,除了【TouchGFX Designer 4.16.0】没有,其他应该都有的。3.基础工程创建打开RTThread中开发板支持程序,我的路径【E:\RTThread\rt-thread\bsp\stm32\stm32f429-atk-apollo】。图1鼠标右击该路径下的空白处,打开Env工具,输入【scons --dist】生成独立的RTThread工程,生成过程大概要几分钟。图2最终会在该目录下生成一个【dist】目录,进入目录之后就是独立的工程文件了,将这个工程文件移到方便操作的目录下,我这里移动到了【E:\RTThread】路径下。图3图44.开发板硬件配置使用【STM32CubeMX】打开如图5的工程,需要修改工程的外设使能和管脚,由于采用的是正点原子的工程,所以很多外设会被默认打开,这里用不到的外设就将其关闭。图5打开【STM32CubeMX】工程后,界面如下,绿色部分都是已经被配置过的管脚,我们需要使用的外设有【LTDC】、【FMC】、【DMA2D】和【USART1】,同时还需要使用【CRC】组件和【TOUCHGFX】软件包;图6首先将其他外设全部关闭,其中【LTDC】、【DMA2D】、【CRC】和【TOUCHGFX】是没有打开的,首先我们打开【DMA2D】,配置如下图7所示:图7再打开LTDC,这里需要配置参数和管脚等,下面几个图是配置完成后的内容。图8图9图10图11接下来配置【FMC】,【FMC】连接的是【SDRAM】,需要配置参数,管脚工程默认配置好了,所以不需要重新配置,下图是配置完成后的内容。图12由于【TOUCHGFX】需要用到【CRC】,所以我们先打开CRC。图13现在开始添加【TOUCHGFX】软件包,点击【Software Packs】->【Select Components】。图14在弹出的窗口中选择【TOUCHGFX】我这里用的是4.16.0版本,第一次使用,需要先下载安装,点击右下角的【OK】关闭窗口。图15这时候就会弹出一个软件包选项,开启后可以看到【TOUCHGFX】,需要配置一下参数,这里采用双缓存,并设置为地址模式,不过这些和【RT-Thread】提供的软件包有冲突,最终不会采用这个配置,配置如下图15所示。图16最后点击右上角的【GENERATE CODE】生成代码即可。图17其实上面的一些外设配置没有意义,因为【RT-Thread】会自己配置一些参数,不会使用【STM32CubeMX】配置的参数,不过也不影响使用,看过之后还能学习一下如何配置。5.导入TouchGFX软件包在工程目录下打开【Env】,输入【menuconfig】命令,打开【RT-Thread】的配置界面,进入【RT-Thread online packages → multimedia packages】打开【touchgfx】软件包。图18退出并保存,返回到【Env】指令界面输入【pkgs --update】下载软件包,下载完成之后,在项目的根目录下会出现一个【packages】目录,这里面有【touchgfx2rtt】的软件包了。图19这里需要修改配置文件,需要【RT-Thread】的配置脚本,修改项目根目录下的【packages\touchgfx2rtt-latest\SConscript】文件,由于有一个头文件路径未添加,这里直接在脚本中将它添上【guiPath = [cwd '/port']】,修改结果如下图20所示。图20我们还需要添加外设配置,需要修改【RT-Thread】的【Kconfig】文件,修改项目根目录下的【board\Kconfig】,需要添加上【LTDC】和【LCD】的配置项,修改结果如下图21所示。图21打开配置文件,配置【RT-Thread】的外设,在【Env】命令行中输入【menuconfig】打开配置,需要打开【SDRAM】外设,配置路径【Hardware Drivers Config → Onboard Peripheral Drivers】。图22打开屏幕相关的外设【LTDC】和【LCD】,配置路径【Hardware Drivers Config → On-chip Peripheral Drivers】。图23最后保存退出,为了让屏幕有显示,先打开项目根目录下的【packages\touchgfx2rtt-latest\TouchGFX\ApplicationTemplate.touchgfx.part】,点击菜单栏中的【Edit->Import GUI】导入下图24中的Demo。图24添加完成之后的效果如下,然后点击右上角的按钮生成代码。图25关闭软件,打开【Env】命令行,输入【scons --target=mdk5】命令,输出MDK工程。打开MDK工程,修改【drv_lcd】文件,将【25~37】和【104~107】行注释掉,如下图26所示。图26将项目根目录下的【board\CubeMX_Config\Src\main.c】文件中的【SystemClock_Config】函数中的内容覆盖项目工程【board.c】文件中的【SystemClock_Config】函数,这一步主要是将【STM32CubeMX】配置的时钟复制到项目中去,如下图27所示。图27最后编译下载到开发板上,效果如图28所示。图286.总结这次移植整体来说不是很难,但是网上没有相关的教程,所以打算做关于TouchGFX和RTThread一系列的教程,当然这只是刚刚开始,后续还有触摸、图片字库存储等一系列操作,本人接触RTThread和TouchGFX也有许久,有困难的童鞋,欢迎私信留言,承接外包~---------------------作者:二哲科技此文章来自于21ic网站,著作权归21ic所有,未经允许禁止转载。▍

    嵌入式ARM 移植 RT-Thread Touch

  • 用国产CH32替代STM32,要不要试试?

    /* 作者: 罗冰  https://blog.csdn.net/luobing4365 */随着芯片价格疯涨,项目的不可控性越来越大。特别是价格方面,达到了无法想象的地步了。按我的记忆,之前项目中所用的STM32F103C8T6,价格在9元左右;而现在单片价格到了惊人的109元!十几倍的涨幅,哪个项目还敢用它?因此,大部分公司,都在准备各种替代方案。我们也一样,预备使用CH32F103C8T6替代STM32F103C8T6。这两种芯片引脚兼容,内部的资源差不多,理论上代码移植也比较方便。我就是这么想的,然后就被打脸了。最大的原因在于,厂家提供的资料太少了!编程相关的CH32F103应用手册,只有短短的31页。我想看的USB设备控制器的寄存器细节,甚至都没有。想想STM32丰富的应用资料、例程和各种视频,感觉从新手级难度到了骨灰级难度了。不过,再想想CH32这友好的价格,也就释然了。周末两天,把之前的USB HID通信,在CH32F103C8T6上实现了,估计不久能很快地应用到项目中去。预计也有不少朋友有类似的需求,我把探索的过程记录下来。1. 固件下载CH32F103的芯片,支持WCH-Link或者其他SW仿真工具下载,也支持使用WCHISPTool通过USB和串口下载。考虑到后续开发的时候需要调试,我使用的是WCH-Link进行下载。如图1所示,给出了WCH-Link的实物图(摘自《WCH-Link使用说明-V1.3》)。图1 WCH-Link实物由于我的目标是使用它下载程序到CH32F103C8T6中,只需要使用ARM模式就行了,不需要关注RISC-V模式。拿到的WCH-Link,一般是RISC-V模式,需要将其切换到ARM模式。模式切换的方法如下:WCH-Link 断电, 将图一正面图 1 中排针, TX 接 GND;WCH-Link 上电, 切换模式成功后, 断开 TX 和 GND;后续使用时, WCH-Link 保持切换后的模式。判断的方法如下:WCH-Link空闲时蓝灯常灭,是为RISC-V模式;WCH-Link空闲时蓝灯常亮,为ARM模式。在ARM模式下,Windows 10下是不需要安装驱动的,而Win7有些情况下需要更换驱动,具体可以向厂家索取资料。图2是WCH-Link在Win7下的设备显示。图2 WCH-Link的ARM模式实际使用中,直接使用SWD协议的两线以及GND就可以下载了。软件的使用方法,可以参考官方提供的《CH32F103评估板说明书》,其中介绍了详细的下载和仿真调试方法。2. 代码编写使用CH32F103C8T6实现之前的USB HID双向通信。在经历了若干款MCU编写USB代码后,对这块内容已经比较熟悉了。简单来说,只要在USB HID的示例上,修改各类描述符,添加需要的命令处理就可以了。可惜的是,厂家提供的示例代码非常少。CH32F103C8T6支持两个USB端口,一个是可做全速主机或设备的USBHD,另一个是全速设备USBD。提供的示例代码中,USBD给出了VirtualCom的工程;USBHD给出了DEVICE、HOSG、HOST_Udisk三个示例。USBD的工程,类似于STM32的Legacy Library;而USBHD的工程,则使用了沁恒电子自己的库。我的目标很明确,实在没太多时间去研究沁恒电子的USB库,因此采用了USBD的示例作为模板,进行开发。由于USBD的工程与STM32的USB库类似,我选择深入研究下STM32的USB库(毕竟资料更多,而且之前学习过)。2.1 STM32的USB-FS Device LibraryUEFI开发探索85中,曾经介绍过如何使用STM32F103C8T6制作HID设备。不过,对于所使用的的USB Library,并没有讨论。STMF103的USB库,可以在STSW-STM32121中找到,其应用文档为UM0424。文档中给出了非常详尽的库说明,如图3为USB库的代码结构。图3 USB库代码结构USB-FS-Device 库主要分为两层:STM32_USB-FS_Device_Driver: 驱动层,访问USB全速设备外围和USB标准协议,兼容USB2.0标准,与STM32标准库分离;这层不能由用户修改;Applicaton Interface:在库和最终用户层之间,提供完成的接口,可以由用户修改;驱动层的代码,大部分情况下是不用修改的,它所包含的源文件说明如下:USB-FS外围部件接口:usb_reg (.h, .c):硬件抽象层usb_int.c:传输中断服务函数usb_mem(.h,.c):数据传输管理USB-FS设备驱动中间层:usb_init (.h,.c) :USB设备初始化全局变量usb_core (.h , .c) :USB协议管理(兼容USB2.0规范第9章) usb_sil (.h,.c) :读写端点的简化函数(USB-FS_Device外围的抽象层)usb_def.h / usb_type.h:用于库中的USB定义和类型platform_config.h:评估板上用到的硬件定义应用层代码是提供给用户修改用的,所需要实现的功能都在此层实现。它所包含的源文件说明如下:usb_conf.h:配置文件usb_desc (.h, .c):描述符usb_prop (.h, .c):应用规范属性usb_endp.c:非控制端口的传输中断处理函数usb_istr (.h,.c):中断处理函数usb_pwr (.h, .c) :电源和连接管理函数对照CH32F103C8T6提供的USBD例程,可以发现其结构与STM32的是一样的。可以断定,它是模仿了STM32的USB Library编写了自己的库函数接口。这种设计方法,对习惯了STM32编程的工程师是非常好的。大部分情况下,可以直接把STM32的示例工程,直接移植到WCH的芯片上来(毕竟STM32的例程还是比较丰富的)。本篇所实现的USB HID双向通信,就是参考了STM32的CustomHID例程,在CH32F103的USBD例子上实现的。2.2 代码移植和修改如图4所示,给出了CH32F103的USBD工程的代码结构。图4 CH32F103的USBD工程代码驱动层的代码完全不用修改。为了确定此事,我对照着STM32的驱动层代码,一个个函数研究了下,除去与芯片相关的部分,其实现代码几乎一致。所要修改的代码在应用层,也不是所有源文件需要修改,需要修改的文件包括三个:usb_desc.c、usb_endp.c和usb_prop.c。看过我UEFI开发探索和YIE002开发探索两个系列博客的网友,应该了解之前我使用STM32开发USB HID设备的过程。而且相关的工程代码,在博客中也提供了(UEFI开发探索85和YIE002开发探索09,前者使用Legacy Library,后者使用Cube Library开发。)。实际的开发过程,与之前的开发过程类似,只不过由于芯片的不同,有些代码需要进行移植。2.2.1 usb_desc.c代码修改所要修改的是各种描述符,包括设备描述符、配置描述符、端点描述符等。需要注意的地方,是CH32F103的最大包长度为8。如下给出了设备描述符和配置描述符等的代码,其余的代码与之前开发的STM32F103工程相同,就不再给出了。#define LOBYTE(x) ((u8)(x

    嵌入式ARM STM32

  • 嵌入式项目可以自动生成?惊现开源生成器

    实际开发中,不同的人习惯用不同的开发工具。比如,对于STM32开发来说,可以用Keil、IAR、VSCode GCC等。比如安富莱开发板的例程中,常常都会提供有Keil及IAR的工程:有没有这样子的一个工具:可以同时生成多种开发环境的工程?偶然间发现的一个工具——project_generator,就是干这事的。什么是project_generator?project_generator是一个嵌入式项目生成器,简称progen。我们可以编写一个特定语法的YAML文件用于管理我们的项目,并根据记录中定义的规则来生成IDE项目文件。progen官网:❝https://pypi.org/project/project-generator/❞progen项目生成器目前支持生成以下工具(IDE、Makefile等)的项目:uVision4 和 uVision5IAR生成文件 (GCC ARM)生成文件 (ARMCC)Cmake (GCC ARM)CoIDE (GCC ARM)Eclipse(带有 GCC ARM 的 Makefile)Sublime(带有 GCC ARM 的 Makefile)Visual Studio(带有 GCC ARM 的 Makefile)安装project_generatorprogen依赖于Python环境,所以需要首先搭建Python环境。可以安装Python2.7.x的,也可以安装Python3.x.x的,大家可以自行安装。安装好Python环境之后,使用pip工具安装project-generator:pip install project-generator安装后得到:project_generator实践官方给我们提供了一个 baremetal blinky examples例子,我们基于这个例子实操一遍。下载baremetal blinky examples得到:projects.yaml就是管理我们工程的yaml文件,这个是总的yaml文件,其会引用到records中的的子yaml文件。通过projects.yaml引用的子yaml文件可以同时管理多个工程,如本例的projects.yaml内容如:projects:  gpio_demo_frdmk64f:    - records/frdm_k64f.yaml  gpio_demo_frdmkl25z:    - records/frdm_kl25z.yaml  gpio_demo_frdmkl46z:    - records/frdm_kl46z.yaml    这里同时管理着gpio_demo_frdmk64f、gpio_demo_frdmkl25z、gpio_demo_frdmkl46z三个工程,records下对应有三个yaml文件:下面通过progen工具来生成工程,因为progen工具是存放在Python/Scrips路径下,并且已经加入到环境变量中,所以progen工具全局有效。我们在baremetal blinky examples工程目录下执行对应命令可生成工程。「1、生成gpio_demo_frdmk64f项目的Keil工程:」progen generate -f projects.yaml -p gpio_demo_frdmk64f -t uvision-p参数指定工程名;-t参数指定生成的工程。「2、生成gpio_demo_frdmk64f项目的IAR工程:」progen generate -f projects.yaml -p gpio_demo_frdmk64f -t iar_arm「3、生成gpio_demo_frdmk64f项目的gcc_arm工程:」progen generate -f projects.yaml -p gpio_demo_frdmk64f -t iar_arm我们知道,keil等工具起初支持的芯片都比较有限,比如需要安装一些芯片支持包进行扩展,同样的,progen也是一样的,支持的开发板及芯片有限,我们可自行添加目标支持。路径如:C:\Python27\Lib\site-packages\project_generator_definitions\target以上就是本次关于progen项目生成器的简要分享。这个工具也是我偶然间知道的,简单了解并实践了一下,顺道分享给大家,并且也是很浅的实践分享。关于yaml里面的内容本文并未提到,大家感兴趣的可以自行去了解学习:❝https://github.com/project-generator/project_generator/wiki/Home❞END来源:嵌入式大杂烩,作者:ZhengNL版权归原作者所有,如有侵权,请联系删除。▍

    嵌入式ARM 嵌入式

  • 嵌入式和单片机的区别到底在哪?

    关注、星标公众号,直达精彩内容来源:网络素材整理:李肖遥单片机和嵌入式,其实没有什么标准的定义来区分他们,对于进行过单片机和嵌入式开发的开发者来说,都有他们自己的定义,接下来,肖遥和就谈谈这两个概念的深入理解。什么是单片机首先明确概念,什么是单片机,单片机是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。从上世纪80年代,由当时的4位、8位单片机,发展到现在的300M的高速单片机。比如最经典的51系列单片机,外观只是一块一个拇指大小的长方体芯片,共40个引脚,里面包含了逻辑运算单元。实际上也就是一个cpu。 在最开始接触单片机的时候,还曾经有过一个疑问,为什么单片机是黑色的而不可以是别的颜色,后来才知道是单片机材料的限制。 对单片机而言,其实一个芯片就是全部,其他的比如单片机最小系统都是为了单片机的正常运作而加入其他元件,比如晶振,5v电源,电感电阻等。当然最小系统只能保证单片机正常运行,几乎实现不了基于单片机的任何应用。为了使单片机实现应用,必须要加入其他外设。比如按键,led灯,led屏,蜂鸣器,各种sensor。这也就是市面上很多公司都在做的单片机开发板。 总结,单片机就是完成运算、逻辑控制、通信等功能的单一模块。也就是单片机真的姓“单”。DSP芯片也可以认为是一个单片机。当然它们性能很强大,但是功能依然很单一,总之就是处理数据、逻辑。  什么是嵌入式那么什么是嵌入式呢,一般说嵌入式都是指嵌入式系统,IEEE(InsTItuteof Electrical and ElectronicsEngineers,美国电气和电子工程师协会)对嵌入式系统的定义:“用于控制、监视或者辅助操作机器和设备的装置”。嵌入式系统是一种专用的计算机系统,作为装置或设备的一部分。通常,嵌入式系统是一个控制程序存储在ROM中的嵌入式处理器控制板。嵌入式系统是将应用程序、操作系统、和计算机硬件在一起的系统,是指以应用为中心,以计算机技术为基础,软硬件可裁剪,其针对的用户应用对功能、可靠性、成本、体积、功耗和使用环境有特殊要求的专用计算机系统。事实上,所有带有数字接口的设备,如手表、微波炉、录像机、汽车等,都使用嵌入式系统,有些嵌入式系统还包含操作系统,但大多数嵌入式系统都是由单个程序实现整个控制逻辑。这是因为嵌入式系统一般用于工业控制,也就是说对外设的控制都是写死的,并不需要人工干预,同时也为了保证系统的稳定和可靠。我们经常可以听到公司招聘的要求是嵌入式软件工程师或者嵌入式硬件工程师,也就是说嵌入式系统包括软件和硬件,其实仔细想想也能明白,都已经跑系统了,当然有软件也有bsp硬件啦。也就是说嵌入式系统是软硬件结合体,国内普遍认同的嵌入式系统定义为:以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。嵌入式的硬件层硬件层中包含嵌入式微处理器、存储器(SDRAM、ROM、Flash等)、通用设备接口和I/O接口(A/D、D/A、I/O等)。在一片嵌入式处理器基础上添加电源电路、时钟电路和存储器电路,就构成了一个嵌入式核心控制模块。其中操作系统和应用程序都可以固化在ROM中。 其中核心就是微处理器,嵌入式处理器和一般的电脑cpu还有区别,嵌入式微处理器大多工作在特定设计的系统中,比如TI或者Atmel公司都有很多定位不同的处理器,atmel的SAM系列是专门为物联网设计的,AVR则由于性能十分突出,广泛应用于工业领域。 嵌入式微处理器有各种不同的体系,即使在同一体系中也可能具有不同的时钟频率和数据总线宽度,或集成了不同的外设和接口。据不完全统计,全世界嵌入式微处理器已经超过1000多种,体系结构有30多个系列,其中主流的体系有ARM、MIPS、PowerPC、X86和SH等。但与全球PC市场不同的是,没有一种嵌入式微处理器可以主导市场,仅以32位的产品而言,就有100种以上的嵌入式微处理器。嵌入式微处理器的选择是根据具体的应用而决定的。在嵌入式领域,可以说arm架构的处理器占据了半壁江山,而arm公司也成为著名的科技公司,而它却没有生产任何处理器,而只是提供了IP,可以看出一流公司做标准。而其他用的比较多的架构就是sparc、powerpc等。比如arm公司有各种各样的处理器架构,最经典的cortex系列,它属于ARMv7架构,这是到2010年为止ARM公司最新的指令集架构。ARMv7架构定义了三大分工明确的系列:“A”系列面向尖端的基于虚拟内存的操作系统和用户应用;“R”系列针对实时系统;“M”系列对微控制器。嵌入式系统和外界交互需要一定形式的通用设备接口,如A/D、D/A、I/O等,外设通过和片外其他设备的或传感器的连接来实现微处理器的输入/输出功能。每个外设通常都只有单一的功能,它可以在芯片外也可以内置芯片中。外设的种类很多,可从一个简单的串行通信设备到非常复杂的802.11无线设备。嵌入式系统中常用的通用设备接口有A/D(模/数转换接口)、D/A(数/模转换接口),I/O接口有RS-232接口(串行通信接口)、Ethernet(以太网接口)、USB(通用串行总线接口)、音频接口、VGA视频输出接口、I2C(现场总线)、SPI(串行外围设备接口)和IrDA(红外线接口)等。这一点其实和单片机类似。嵌入式的软件层也就是操作系统了,包括内核和文件系统,还有就是更为顶层的应用程序,嵌入式操作系统一般都是Linux或者其他类Unix,还有一些实时操作系统(RTOS)比如VxWorks、RTEMS、ucOS等。其中Linux还包括不同的distribuTIon,比如Ubuntu、Redhat、Debian、centos等,他们都是采用Linux的内核,不同的是上面的software和tools,当然不用太过于担心标准问题,这些Linux发行版选择的软件几乎都是比较通用的,比如网页服务器的Apache、电子邮件服务器的postfix、sendmail、文件服务器的Samba等。此外还有Linuxstandard base等标准来规范开发者。类Unix主要是FreeBSD以及Solaris等。嵌入式领域最常用的还是一些实时操作系统,实时操作系统的核心就是实时性,本质就是任务处理所华为时间的可预测性,即任务需要在规定内时限内完成。IEEE对实时系统的定义是“那些正确性不仅取决于计算的逻辑结果也取决于产生结果所花费时间的系统”。实时操作系统有硬实时和软实时之分,硬实时要求在规定的时间内必须完成操作,这是在操作系统设计时保证的;软实时则只要按照任务的优先级,尽可能快地完成操作即可。我们通常使用的操作系统在经过一定改变之后就可以变成实时操作系统。那么实时操作系统和Linux这种分时操作系统的区别列举如下:(1)多路性。实时信息处理系统与分时系统一样具有多路性。系统按分时原则为多个终端用户服务;而对实时控制系统,其多路性则主要表现在经常对多路的现场信息进行采集以及对多个对象或多个执行机构进行控制。(2)独立性。实时信息处理系统与分时系统一样具有独立性。每个终端用户在向分时系统提出服务请求时,是彼此独立的操作,互不干扰;而在实时控制系统中信息的采集和对对象的控制,也彼此互不干扰。(3)及时性。实时信息系统对实时性的要求与分时系统类似,都是以人所能接受的等待时间来确定;而实时控制系统的及时性,则是以控制对象所要求的开始截止时间或完成截止时间来确定的,一般为秒级、百毫秒级直至毫秒级,甚至有的要低于100微秒。(4)交互性。实时信息处理系统具有交互性,但这里人与系统的交互,仅限于访问系统中某些特定的专用服务程序。它不像分时系统那样能向终端用户提供数据处理服务、资源共享等服务。(5)可靠性。分时系统要求系统可靠,相比之下,实时系统则要求系统高度可靠。因为任何差错都可能带来巨大的经济损失甚至无法预料的灾难性后果。因此,在实时系统中,采取了多级容错措施来保证系统的安全及数据的安全。由于更加可靠和及时。嵌入式实时操作系统更加广泛应用于工业控制、航空航天、军工等领域,比如美国航天局NASA近几年发射的火星探测器等都是采用的RTEMS实时操作系统。嵌入式的中间层所谓的中间层就是软件层和硬件层之间的接口层,其实严格而言也属于软件层。一般开发者称之为BSP,这一层主要负责的是向下提供硬件的驱动,硬件的配置等操作,向上则向软件开发者提供标准API,进行中间层开发的开发者通常称为嵌入式驱动工程师。从这里也可以看出来,嵌入式设计和软硬都分不开,既要掌握底层硬件的特性以及如何驱动其工作,也要了解操作系统的相关知识,才可以编写相应功能的应用。因此看一个操作系统是否支持某个芯片或者某个开发板,只要看其源码中是否包含相应芯片或开发板的板级支持包。嵌入式系统应该跑在什么样的硬件上谈起嵌入式硬件或者开发板,我想很多人第一印象就是RaspberryPi,是一块只有信用卡大小的微型电脑,别看其外表“娇小”,内“心”却很强大,视频、音频等功能通通皆有,可谓是“麻雀虽小,五脏俱全”。树莓派推出后,很多厂商争相推出类似产品,比如香蕉派之类的。还有TI的Beagleboneblack板子,体积大小和树莓派类似,外设包括有USBhost和USBmini以及网卡接口,背面还有一个sd卡槽和HDMI接口。BBB的处理器采用的是当前嵌入式系统中最流行的ARMv7指令集。采用当今广泛使用的指令集的处理器可以被更多的软件支持。例如,一些操作系统已经不支持在ARMv6指令集上运行,例如,Ubuntu在2012年4月放弃了对ARMv6指令集的支持。 ARMv7相对与ARMv6指令集的另一个优势在于,使用ARMv7的处理器的实际性能更加强劲。ARMv7相对与ARMv6的优势还有很多,比如一些显著的改进:实现了超标量架构、包含了SIMD操作指令、改进了分支预测算法从而极大的提高了某些性能。 总结以上就是一块基本的嵌入式核心板所具有的性能参数,和上面说到的单片机的性能参数相比较,单片机的处理能力较低,主频大多在几十M上下,和嵌入式动辄上百上千M的处理速度还是相差较多,此外单片机并不具有图形界面的处理能力,也就是GPU的缺乏导致单片机几乎不可能带动图形界面。单片机的存储空间和嵌入式处理器也不是一个等级的,单片机通常片内存储只有几k大小,而由于外设的限制也不太可能大范围增加外设emmc,而嵌入式处理器通常有几百兆的RAM,如此巨大的差别导致单片机几乎不可能像嵌入式处理器那样运行操作系统,甚至连TCP/IP协议栈和USB协议栈都跑不起来,一些高端的单片机比如ST公司的STM32系列,可能可以跑一些轻量级的系统os和嵌入式网络协议栈,比如IwIP协议栈。嵌入式处理器丰富强大的性能决定它能完成更多单片机不能完成的应用,比如网络通信功能,视频传输处理功能等,而当外设存储增加后,嵌入式处理器能够轻松运行各种Linux系统,以及图形GUI界面。在开发方式上单片机和嵌入式也有较大差别,也就是编译过程的区别,单片机主要在Windows等图形界面下开发,目前有很多成熟的IDE工具比如keil、IAR、以及ti的CCS等,这些工具集编译、汇编、链接、仿真为一体,并且由于在Windows下开发,具有友好的用户界面,开发者只需编写c代码,然后点击编译链接按键即可,出现错误还可以debug或者仿真,上手还是非常快的。而嵌入式开发一般是在Linux下进行的,要将c代码在自己主机上编译完成,然后通过系统镜像或者uboot引导将编译好的文件烧入开发板,由于主机的处理器的x86架构,而编写的代码是为了运行在arm架构或sparc架构的处理器上,因此存在一个交叉编译链的安装,此外,Linux下没有Windows那样的IDE,也就是编译,链接源代码都需要开发者自己完成,一般都是利用GNUmake脚本编写Makefile以及configure文件来完成,Makefile文件中编写如何对c或者h文件编译,也就是编译规则以及依赖文件是什么。这些都需要开发者自己完成。并且以上过程都是在Linux下的终端也就是命令行中完成,这也给嵌入式开发增加了难度。来源整理于网络素材,版权归原作者所有,如有侵权,请联系删除,谢谢。‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧关注我的微信公众号,回复“加群”按规则加入技术交流群。欢迎关注我的视频号:点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

    技术让梦想更伟大 嵌入式 单片机

  • 适合嵌入式的C 开源项目-Workflow

    在来腾讯之前,我是没有真正用C 开发过一个完整的项目的,然后,赶鸭子上架,鹅厂的人特别喜欢用C ,而且用的特别好。我这次推荐一个开源的C 项目,希望喜欢C ,或者想往C 方向发展的同学可以看看。面向过程一定是偏向底层的,面向对象一定是偏向应用的,别杠!下面介绍下这个C 开源项目项目名称: Workflow项目地址:https://github.com/sogou/workflow这个项目适合用于什么场景这个项目是搜狗的服务器引擎,几乎搜狗所有的后端C 服务和其他几十家公司都在使用这个引擎,每日处理超百亿请求。不仅如此,该项目绝对也非常适合在嵌入式应用上实现网络的框架设计,框架设计是一个项目成功与否非常重要的环节,而网络在AIOT、智能家居、智能硬件上尤其重要。比如我们在项目中需要使用http,Workflow在设计上可以通过Cmake配置支持windows 或者 Linux 或 MacOS 等多个平台上的多个项目编译,而且,在不同平台上保持相同的接口。如果你新增一个嵌入式设备,比如ESP32,在服务器和客户端,你只需要维护一套代码,就可以完成自己的应用需求。真正做到把云端的技术移植到嵌入式设备上运行。适用于嵌入式几大特点1. ⽀持多平台、多体系结构多平台是⼀个⾮常重要的特性,多平台的代码说明在配置和耦合上考虑了很多设计上的细节。⽬前Workflow除了Linux、Windows、MacOS、Android以外,还可以愉快地在树莓派、国产⻰芯处理器等不同体系结构上运⾏。2. 编译快Workflow除OpenSSL以外不依赖其他库,⽽且在接⼝层是没有模版的,因此编译速度⾮常快,不到⼀分钟即可编出⼀个可⽤的lib。3. 体积⼩、⽀持编译裁剪Workflow的Kafka协议默认是不编译的,除此之外,还可以裁剪掉其他不常⽤的模块。⽐如:make REDIS=n MYSQL=n UPSTREAM=n并且可以通过strip命令去掉符号链接,即可让库⽂件缩⼩到400k左右。4. 运⾏时内存⼩、调度快作为⼀个异步调度的库,Workflow调度性能⼀直是⾮常好的。另外运⾏时内存占⽤也⾮常的⼩。在默认配置下,tutorial 下的 helloworld server 服务占用内存大小测试运行服务查找进程号查看内存占用,物理内存占用在3824kb5. ⾃定义协议⾮常⽅便另外,社区活跃以及项目负责人积极回复问题我觉得也是一个亮点,如果在框架搭建和移植上出现问题,在社区上提问都会得到快速响应。细说编译与自定义协议开源应该是我推荐最主要的原因,还有一个原因是,这个项目非常适合在一些大型的嵌入式项目中使用。我之前创业,在通讯协议上花了很长的时间,如果当时发现这个项目,我觉得套用这个框架,可能会轻松很多。这个项目使用Cmakefile来配置,之前发的Cmakefile文档我觉得也是对大家有用的。嵌入式杂谈之makefile补充Cmake确实应该用到的时候再学我当时创业的项目就是存在很多客户端,然后嵌入式客户端会和服务器进行交互数据。因为这个原因,我们就自己定义协议,并在我们自己的协议上做了很多规避措施,当然,这些措施都没有经过时间的捶打。但Workflow的这个⾃定义协议的例⼦,直接在教程⾥就给出了,感兴趣的朋友可以下载完后通过以下命令,即可编译出⼀个⾃定义协议的server和client:makecd tutorialmake./tutorial-10-user_defined_protocol/server另⼀个窗⼝执⾏client,即可通信:./tutorial-10-user_defined_protocol/client执行过程:移植到嵌入式设备的开源支持据悉⽬前已经有许多⽤户⽤到了嵌⼊式上了。我到github上翻阅发现,遇到问题的时候,官方还特别耐心的解释,真的很不错,这才是做技术该有的样子嘛~项目的例程官方给的实例比较多,新手的同学建议都看看,从这些东西入手会让自己更快进入状态。一个简单的Client 和 Server 端的代码服务端:#include #include "workflow/WFHttpServer.h"int main() {    WFHttpServer server([](WFHttpTask *task) {        task->get_resp()->append_output_body("Hello World!");    });    if (server.start(8888) == 0) { // start server on port 8888        getchar(); // press "Enter" to end.        server.stop();    }    return 0;}客户端:int main(int argc, char *argv[]) {        WFMySQLTask *task = WFTaskFactory::create_mysql_task(url, RETRY_MAX, mysql_callback);    task->get_req()->set_query("SHOW TABLES;");        task->start();}任务流框架设计在作者的设计理念中,一切业务逻辑皆是任务,多个任务会组成任务流,任务流可组成图,这个图可能是串联图,可能是并联图,也有可能是串并联图,类似于这种:也有可能是这种复杂的DAG图:当然图的层次结构可由用户自定义,个人认为框架最牛逼的一点就是支持动态创建任务流。这个项目大概就介绍到这里再说下这个项目的资料汇总参考资料:https://zhuanlan.zhihu.com/p/358869362https://zhuanlan.zhihu.com/p/165638263项目地址如下:https://github.com/sogou/workflow 也可以点击阅读原文直达。在访问GitHub遇到困难时,可使用他们的Gitee官方仓库:https://gitee.com/sogou/workflow感觉这个项目值得学习的话就给人家个star,不要白嫖哈,对项目团队来说也是一种认可和鼓励。

    技术让梦想更伟大 嵌入式 开源

首页  上一页  1 2 3 4 5 6 7 8 9 10 下一页 尾页
发布文章