tag 标签: 嵌入式软件

相关帖子
相关博文
  • 热度 3
    2024-10-12 11:21
    235 次阅读|
    0 个评论
    01 I2C总线介绍 I2C(Inter-Integrated Circuit)通信总线,作为嵌入式系统设计中的一个关键组成部分,其灵活性和高效率使其在高级应用中备受青睐。本文旨在提供关于I2C通信总线的深度解析,包括其基本概念、特点、通信协议,以及在不同场景下的高级应用和最佳实践。I2C接口只有2根信号线,总线上可以连接多个设备,硬件实现简单,可扩展性强。I2C通信协议可以用普通GPIO引脚进行软件模拟。I2C接口主要用于通讯速率要求不高,以及多个器件之间通信的应用场景。 02 I2C总线历史 I2C(Inter-Integrated Circuit)总线是一种重要的串行通信协议,它的历史可以追溯到上世纪80年代初期。以下是对I2C总线历史的详细介绍: 起源: I2C总线技术由荷兰的飞利浦半导体(现在的恩智浦半导体)在1982年开发。最初,这项技术是为了在电视机内部实现简单、高效、低成本的通信而设计的。 设计目标: 设计I2C的初衷是减少电视机等复杂电子系统内部的布线数量,同时也降低制造成本。通过使用只有两根线的通信总线,它有效地减少了器件间连接的复杂性。 技术发展: 随着技术的成熟和普及,I2C协议得到了广泛的应用和扩展。从最初的标准模式(100kHz),发展到快速模式(400kHz)和高速模式(3.4MHz)。 标准化和开放: 虽然最初由飞利浦半导体开发,但I2C协议后来被标准化并广泛应用于多种设备中。飞利浦半导体放弃了对这项技术的专利权,使其成为开放标准。 广泛应用: I2C技术由于其简单性和有效性,已成为嵌入式系统设计中不可或缺的一部分。 03 I2C通信总线基本概念 I2C是一种多主机、两线制、低速串行通信总线,广泛用于微控制器和各种外围设备之间的通信。它使用两条线路:串行数据线(SDA)和串行时钟线(SCL)进行双向传输。 特点 两线制总线:I2C仅使用两条线——串行数据线(SDA)和串行时钟线(SCL)进行通信,有效降低了连接复杂性。 多主多从设备支持:I2C支持多个主设备和多个从设备连接到同一总线上。每个设备都有唯一的地址。 可变的时钟速率:I2C总线支持不同的速率模式,如标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps)。 同步通信:I2C是一种同步通信协议,数据传输由时钟信号(SCL)来控制。 简单的连接:I2C通信对硬件的要求比较低,很容易在微控制器和外围设备间实现连接。 地址分配:每个I2C设备都通过一个7位或10位的地址来识别,这使得总线上可以连接多个设备。 阻塞传输:I2C支持阻塞传输机制,即主设备可以在传输过程中控制总线,防止其他设备发送数据。 应用广泛:由于其简单和灵活性,I2C被广泛应用于各种电子产品中,如传感器、LCD显示器、EEPROM等。 总线仲裁和冲突检测:在多主模式下,I2C能够处理多个主设备同时尝试控制总线的情况。 低功耗:I2C总线的设计使其成为低功耗的通信方式,适用于电池供电的设备。 基本特征 总线结构: 两线制:使用两条线进行通信,分别是串行数据线(SDA)和串行时钟线(SCL)。 多主多从结构:支持多个主设备和多个从设备连接到同一总线上。 通信方式: 同步串行:数据传输同步于时钟信号。 字节格式:每个字节由8位数据构成,加上开始和停止条件以及可选的应答位。 时钟速率: 支持多种速率,包括标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps)。 工作原理 总线控制: 开始和停止条件:通信由主设备通过在SDA线上生成特定的信号模式来开始和结束。 地址帧:每次通信开始时,主设备发送一个地址帧来指定与之通信的从设备。 数据传输: 主从通信:主设备控制时钟信号,向从设备发送或接收数据。 应答位:每个字节后,接收方 发送一个应答位(ACK)或非应答位(NACK),以告知发送方数据是否被成功接收。 地址和仲裁 设备地址: 7位或10位地址:每个I2C设备都有一个唯一的地址,允许在同一总线上连接多个设备。 总线仲裁: 在多主模式下,当两个主设备同时尝试控制总线时,I2C协议包含仲裁机制以决定哪个设备获得控制权。 04 I2C数据传输流程 数据信号以8位的序列传输。所以在特殊的开始条件发生后,就会出现第一个8位序列,它指示了数据被发送到哪个从设备的地址。每个8位序列之后都会跟随一个称为确认的位。 在大多数情况下,第一个确认位之后会跟着另一个寻址序列,但这次是针对从设备的内部寄存器。在寻址序列之后是数据序列,直到数据完全传输完毕,并以一个特殊的停止条件结束。 开始条件发生在数据线在时钟线仍然高电平的时候变低。之后,时钟开始,并且在每个时钟脉冲期间传输每一位数据。设备寻址序列从最重要的位开始,以最不重要的位结束,实际上是由7位组成的,因为第8位用于指示主设备是向从设备写入(逻辑低)还是从中读取(逻辑高)。 下一个确认位由从设备用来指示它是否成功接收了前一个位序列。所以这次主设备将SDA线的控制权交给从设备,如果从设备成功接收了前一个序列,它将把SDA线拉低到所谓的确认状态。 如果从设备没有把SDA线拉低,这种状态被称为不确认,意味着它没有成功接收前一个序列,这可能由多种原因造成。例如,从设备可能正忙,可能不理解接收到的数据,或者不能再接收任何数据等等。 在这种情况下,主设备决定如何继续操作。 接下来是内部寄存器的寻址。内部寄存器是从设备内存中包含各种信息或数据的位置。 例如,ADXL345加速度计有一个独特的设备地址和额外的内部寄存器地址,用于X、Y和Z轴。 因此,如果我们首先想读取x轴的数据,我们需要发送设备地址,然后发送x轴的特定内部寄存器地址。这些地址可以从传感器的数据手册中找到。 在寻址之后,数据传输序列开始,要么来自主设备,要么来自从设备,这取决于在读/写位选择的模式。 在数据完全发送之后,传输将以停止条件结束,当SDA线在SCL线高电平时从低变高。这就是I2C通信协议的工作原理。 上述内容出现了很多特定概念,我们下面来分别解释他们: SDA和SCL信号 SDA和SCL都是双向线路,通过电流源或上拉电阻连接到正电源电压(见图3)。 当总线空闲时,两条线路都是HIGH。连接到总线的设备的输出级必须具有开漏极或开集电极来执行有线与功能。I2C总线上的数据可以在标准模式下以高达100 kbit/s的速度传输,在快速模式下可达400 kbit/s,在快速模式+中可达1 Mbit/s,或在高速模式下可达3.4 Mbit/s。总线电容限制了连接到总线的接口数量。 对于单个主应用程序,如果总线上没有设备会拉伸时钟,主SCL输出可以是推挽驱动器设计。 数据有效性 SDA线上的数据必须在时钟HIGH期间保持稳定。只有当SCL线上的时钟信号为LOW时,数据线的HIGH或LOW状态才能改变(见图4)。传输的每个数据位都会产生一个时钟脉冲。 START和STOP条件 所有事务都以START(S)开始,并由STOP§终止(参见图5)。 SDA线上SCL为HIGH时,HIGH到LOW的转换定义了一个START条件。 SDA线上SCL为HIGH时,LOW到HIGH的转换定义了一个STOP条件。 START和STOP条件总是由主设备产生。在START条件之后,总线被认为是忙碌的。在STOP条件之后的某个时间,总线被认为是空闲的。 如果产生了重复的START(Sr)而不是STOP条件,总线保持忙碌。在这方面,START(S)和重复的START(Sr)条件在功能上是相同的。 因此,对于本文档的其余部分,S符号被用作代表START和重复的START条件的通用术语,除非Sr是特别相关的。 如果连接到总线的设备合并了必要的接口硬件,则检测START和STOP条件是容易的。然而,没有这种接口的微控制器必须在每个时钟周期内对SDA线采样至少两次,以感知转换。 字节格式 每一个放在SDA线上的字节必须是8位长。每次传输可以传输的字节数是没有限制的。每一个字节后面必须跟一个确认位。数据以最有效位(MSB)为首进行传输(见图6)。如果一个从机在执行其他一些功能之前不能接收或传输另一个完整的字节数据,例如处理一个内部中断,它可以保持时钟线SCL LOW,迫使主机进入等待状态。当从机准备好接收另一个字节数据并释放时钟线SCL时,数据传输继续进行。 Acknowledge(ACK)和Not Acknowledge(NACK) 确认发生在每个字节之后。确认位允许接收端向发送端发出信号,表示字节被成功接收,可以发送另一个字节。主设备产生所有的时钟脉冲,包括确认的第九个时钟脉冲。 确认信号定义如下:发送端在确认时钟脉冲期间释放SDA线,这样接收端就可以拉SDA线LOW,并且在该时钟脉冲的HIGH期间保持稳定的LOW(参见图4)。设 当SDA在第九个时钟脉冲期间保持HIGH时,这被定义为不确认信号。主设备然后可以产生一个STOP条件来中止传输,或者重复的START条件来启动一个新的传输。有五个条件导致NACK的产生: 没有接收器在总线上传输地址,所以没有设备响应确认。 接收器无法接收或发送,因为它正在执行一些实时功能,并且还没有准备好与主服务器进行通信。 在传输过程中,接收方收到了它无法理解的数据或命令。 在传输过程中,接收方不能接收到任何更多的数据字节。 主接收机必须向从发送机发出传输结束的信号。 时钟同步 两个主控器可以同时在一个空闲总线上开始传输,必须有一种方法来决定哪个主控器控制总线并完成传输。这是通过时钟同步和仲裁来实现的。在单主控器系统中,时钟同步和仲裁是不需要的。 时钟同步是通过I2C接口到SCL线的有线与连接来实现的。这意味着SCL线上的HIGH到LOW转换会导致相关的主控器开始计数他们的LOW周期,一旦主控器时钟变为LOW,它会保持SCL线处于该状态,直到时钟达到HIGH状态(见图7)。然而,如果另一个时钟仍然在它的LOW周期内,这个时钟的LOW到HIGH转换可能不会改变SCL线的状态。因此,SCL线被具有最长LOW周期的主控器保持为LOW。具有较短LOW周期的主控器在这段时间内进入HIGH等待状态。 当所有主控机都结束了低周期时,SCL线释放并变为高电平。此时主控机时钟与SCL线的状态没有区别,所有主控机开始计算它们的高周期。第一个完成高周期的主控机再次拉低SCL线。 这样,一个同步SCL时钟就产生了,它的低周期由低周期最长的主控机决定,而它的高周期由高周期最短的主控机决定。 仲裁 仲裁,像同步一样,是指只有在系统中使用多个主设备时才需要的协议部分。从设备不参与仲裁过程。只有总线空闲时,主设备才可以开始传输。两个主设备可以在最小保持时间(tHD; STA)内产生一个START条件,这会导致总线上产生一个有效的START条件。然后需要仲裁来决定哪个主设备将完成它的传输。 仲裁逐位进行。在每个位期间,当SCL为HIGH时,每个主设备检查SDA电平是否与它所发送的相匹配。这个过程可能需要许多位。两个主设备实际上可以无误地完成整个事务,只要传输是相同的。第一次一个主设备试图发送HIGH,但检测到SDA电平为LOW,主设备知道它已经失去了仲裁并关闭SDA输出驱动器。另一个主设备继续完成它的事务。 在仲裁过程中没有信息丢失。一个失去仲裁的主设备可以产生时钟脉冲,直到它失去仲裁的字节结束,并继续产生时钟脉冲。必须在总线空闲时重新开始它的事务。 如果一个主设备也包含一个从设备功能,并且它在寻址阶段失去仲裁,有可能是获胜的主设备试图寻址它。因此,失败的主设备必须立即切换到它的从设备模式。 图8显示了两个主设备的仲裁过程。可能涉及更多内容,这取决于总线连接了多少主设备。当产生DATA1的主设备的内部数据电平与SDA线上的实际电平之间存在差异时,DATA1输出被关闭。这不影响由获胜的主设备发起的数据传输。 由于I2C总线的控制完全由竞争主设备发送的地址和数据决定,所以没有中心主设备,总线上也没有任何优先顺序。 如果仲裁程序仍在进行,当一个主设备发送重复的START或STOP条件,而另一个主设备仍在发送数据时,则存在一个未定义的条件。换句话说,以下组合会导致一个未定义的条件: 主设备1发送重复的START条件,主设备2发送一个数据位。 主设备1发送STOP条件,主设备2发送一个数据位。 主设备1发送重复的START条件,主设备2发送一个STOP条件。 时钟拉伸 时钟拉伸通过保持SCL线LOW暂停事务。事务无法继续,直到该线再次释放为HIGH。时钟拉伸是可选的,事实上,大多数从设备不包括SCL驱动程序,因此它们无法拉伸时钟。 在字节级,设备可能能够以较快的速度接收字节数据,但需要更多的时间来存储接收到的字节或准备传输另一个字节。从设备可以在接收和确认一个字节后保持SCL线LOW,以迫使主设备进入等待状态,直到从设备准备好在一种握手过程类型中进行下一个字节传输(见图7)。 在位级,设备如微控制器,具有或不具有I2C总线有限的硬件,可以通过延长每个时钟LOW周期来减慢总线时钟。任何主设备的速度都适应于该设备的内部运行速率。 从地址和R/W位 数据传输遵循图9所示的格式。在START条件(S)之后,发送一个从地址。这个地址是7位长,后面跟着第八位,这是一个数据方向位(R/W)——“0”表示传输(WRITE),“1”表示数据请求(READ)(参见图10)。数据传输总是由master生成的STOP条件§终止。然而,如果master仍然希望在总线上通信,它可以生成一个重复的START条件(Sr)并在没有首先生成STOP条件的情况下寻址另一个从设备。在这样的传输中,各种读/写格式的组合是可能的。 10位寻址 10位寻址扩展了可能的地址数。具有7位和10位地址的设备可以连接到同一个I2C总线,并且7位和10位寻址都可以在所有总线速度模式下使用。目前,10位寻址还没有被广泛使用。 10位从属地址是由一个START条件(S)或重复的START条件(Sr)之后的前两个字节组成的。 第一个字节的前7位是组合1111 0XX,其中最后两个位(XX)是10位地址的两个最有效位(MSB);第一个字节的第八位是R/W位,它决定了消息的方向。 虽然有8个可能的保留地址位1111 XXX的组合, 但只有四个组合1111 0XX用于10位寻址。其余四个组合1111 1XX被保留用于未来的I2C总线增强。 所有先前描述的7位寻址的读/写格式组合都可能用10位寻址。这里详细介绍两种格式: 主发送器用一个10位从属地址向从属接收器发送。 传输方向不变(见图14)。当一个10位地址跟随一个START条件时,每个从属比较从属地址第一个字节的前7位(1111 0XX)与自己的地址,并测试第八位(R/W方向位)是否为0。可能有多个设备找到一个匹配并产生一个确认(A1)。所有找到匹配的从属比较从属地址第二个字节的八位(XXXX XXXX)与自己的地址,但只有一个从属找到一个匹配并产生一个确认(A2)。匹配的从属仍然由主寻址,直到它接收到一个STOP条件§或重复的START条件(Sr),后面跟着一个不同的从属地址。 主接收器用一个10位从属地址读取从属发送器。 传输方向在第二个R/W位之后改变(图15)。直到并包括确认位A2,过程与前面描述的用于一个从属发送器的程序相同。主发送器寻址从接收器。在重复的START条件(Sr)之后,匹配的从设备记住它之前被寻址过。这个从设备然后检查Sr之后的从地址的第一个字节的前7位是否与它们在START条件(S)之后是相同的,并测试第八位(R/W)是否为1。 如果有匹配,从设备认为它被作为一个发送器寻址,并产生确认A3。从发送器保持寻址状态,直到它接收到一个STOP条件§或接收到另一个重复的START条件(Sr)后跟随一个不同的从地址。在重复的START条件(Sr)之后,所有其他从设备也将比较从地址(1111 0XX)的第一个字节的前7位与它们自己的地址,并测试第八位(R/W)。 然而,它们中没有一个被寻址,因为R/W=1(10位设备),或者1111 0XX从地址(7位设备)不匹配。 具有10位寻址的从设备对“通用调用”的反应与具有7位寻址的从设备相同。硬件主设备可以在“通用调用”后传输其10位地址。在这种情况下,“通用调用”地址字节后面跟着两个连续的字节,其中包含主发送器的10位地址。格式如图15所示,第一个数据字节包含主地址的最低有效位8位。 开始字节0000 0001 (01h)可以以与7位地址相同的方式出现在10位地址之前。 通用调用地址 通用调用地址用于同时寻址连接到I2C总线的每个设备。然而,如果一个设备不需要通用调用结构中提供的任何数据,它可以通过不发出确认来忽略这个地址。如果一个设备确实需要来自通用调用地址的数据,它会确认这个地址并表现为从接收器。如果一个或多个设备响应,主设备实际上不知道有多少设备确认。第二个字节和后续字节被每一个能够处理此数据的从接收器确认。一个不能处理这些字节之一的从设备必须通过不确认来忽略它。同样,如果一个或多个从设备确认,主设备将不会看到不确认。通用调用地址的含义总是在第二个字节中指定(见图16)。 有两种情况需要考虑: 当最低有效位B为“0”时。 当最低有效位B为“1”时。 当位B为“0”时,第二个字节有以下定义: 0000 0110 (06h):硬件复位并写入从地址的可编程部分。在接收到这个2字节序列时,所有设计用于响应通用调用地址的设备都复位并接收其地址的可编程部分。 必须采取预防措施,以确保设备在施加电源电压后没有拉下SDA或SCL线,因为这些低电平会阻塞总线。 0000 0100 (04h):硬件写入从地址的可编程部分。 行为与上述相同,但设备不复位。 0000 0000 (00h):此代码不允许用作第二个字节。 编程过程的序列在适当的设备数据表中公布。其余代码尚未固定,设备必须忽略它们。 当位B为“1”时,2字节序列是“硬件通用调用”。这意味着该序列由硬件主设备传输,例如键盘扫描器, 它可以被编程来传输所需的从地址。由于硬件主设备事先并不知道消息必须被传输到哪个设备,它只能生成这个硬件通用调用和它自己的地址 — 向系统标识它自己(参见图 17)。 第二个字节中剩下的七位包含硬件主机的地址。 这个地址被连接到总线的智能设备(例如,微控制器)识别,然后接受来自硬件主机的信息。如果硬件主机也可以充当从机,从机地址与主机地址相同。 在某些系统中,另一种方法是将硬件主发射机在系统复位后设置为从接收机模式。这样,系统配置主可以告诉硬件主发射机(现在处于从接收机模式)必须发送数据到哪个地址(见图18)。在编程程序之后,硬件主保持在主发射机模式。 开始字节 微控制器可以以两种方式连接到I2C总线。带有片上硬件I2C总线接口的微控制器可以被编程为只被总线请求中断。当设备没有这样的接口时,它必须通过软件不断地监视总线。显然,微控制器监视或轮询总线的次数越多,它执行预定功能的时间就越少。 因此,在快速的硬件设备和相对较慢的依赖于软件轮询的微控制器之间存在速度差异。 在这种情况下,数据传输可以先由一个比正常情况长得多的启动过程(见图19)。启动过程包括: 一个开始条件(S) 一个开始字节(0000 0001) 一个确认时钟脉冲(ACK) 一个重复的开始条件(Sr) 在需要总线访问的主机传输了START条件S之后, 传输START字节(0000 0001)。另一个微控制器因此可以以低采样率对SDA线进行采样,直到检测到START字节中的7个零之一。在检测到SDA线上的LOW电平后,微控制器可以切换到更高的采样率,以找到重复的START条件Sr,然后用于同步。 硬件接收器在接收到重复的START条件Sr后重置,因此忽略START字节。 在START字节后生成一个与确认相关的时钟脉冲。这只是为了符合总线上使用的字节处理格式。不允许任何设备确认START字节。 设备ID 设备ID字段(见图20)是一个可选的3字节只读(24位)字,提供以下信息: 12位制造商名称,每个制造商(例如NXP)都是唯一的 9位部件标识,由制造商指定(例如PCA9698) 3位模具修订,由制造商指定(例如RevX) 设备ID是只读的,硬连接在设备中,可以按如下方式访问: START 条件 主控器发送保留设备ID I2C总线地址,后面跟着设置为‘0’的R/W位(写入):“1111 1000”。 主设备发送它必须识别的从设备的I2C总线从地址。LSB是一个“不关心”的值。只有一个设备必须确认这个字节(具有I2C总线从地址的设备)。 主设备发送一个Re-START条件。 备注:一个STOP条件跟随一个START条件重置从设备的状态机,设备ID读取无法执行。同样,一个STOP条件或一个Re-START条件跟随访问另一个从设备重置从设备的状态机,设备ID读取无法执行。 主控器发送保留设备ID I2C总线地址,后面跟着设置为‘1’的R/W位:‘1111 1001’。 设备ID读取可以完成,从12个制造商位(第一个字节+第二个字节的四个MSB)开始,接下来是9个部件识别位(第二个字节的四个LSB+第三个字节的五个MSB),然后是三个模具修正位(第三个字节的三个LSB)。 主设备通过ACK最后一个字节结束读取序列,从而重置从设备的状态机并允许主设备发送STOP条件。 备注:设备ID的读取可以通过发送一个ACK在任何时候停止。 如果主设备继续ACK第三个字节后的字节,从设备回滚到第一个字节并继续发送设备ID序列,直到检测到一个ACK。 05 I2C传输数据的格式 5.1 写操作 流程如下: 主芯片要发出一个start信号 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读) 从设备回应(用来确定这个设备是否存在),然后就可以传输数据 主设备发送一个字节数据给从设备,并等待回应 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。 数据发送完之后,主芯片就会发送一个停止信号。 下图:白色背景表示"主→从",灰色背景表示"从→主" 5.2 读操作 流程如下: 主芯片要发出一个start信号 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读) 从设备回应(用来确定这个设备是否存在),然后就可以传输数据 从设备发送一个字节数据给主设备,并等待回应 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。 数据发送完之后,主芯片就会发送一个停止信号。 下图:白色背景表示"主→从",灰色背景表示"从→主" 5.3 I2C信号 I2C协议中数据传输的单位是字节,也就是8位。但是要用到9个时钟:前面8个时钟用来传输8数据,第9个时钟用来传输回应信号。传输时,先传输最高位(MSB)。 开始信号(S):SCL为高电平时,SDA山高电平向低电平跳变,开始传送数据。 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。 响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化 I2C协议信号如下: 5.4 协议细节 如何在SDA上实现双向传输? 主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面必然有两个引脚(发送引脚/接受引脚)。 主、从设备都可以通过SDA发送数据,肯定不能同时发送数据,怎么错开时间? 在9个时钟里, 前8个时钟由主设备发送数据的话,第9个时钟就由从设备发送数据; 前8个时钟由从设备发送数据的话,第9个时钟就由主设备发送数据。 双方设备中,某个设备发送数据时,另一方怎样才能不影响SDA上的数据? 设备的SDA中有一个三极管,使用开极/开漏电路(三极管是开极,CMOS管是开漏,作用一样),如下图: 真值表如下: 从真值表和电路图我们可以知道: 当某一个芯片不想影响SDA线时,那就不驱动这个三极管 想让SDA输出高电平,双方都不驱动三极管(SDA通过上拉电阻变为高电平) 想让SDA输出低电平,就驱动三极管 5.5 示例:主设备发送(8bit)给从设备 从下面的例子可以看看数据是怎么传的(实现双向传输)。 举例:主设备发送(8bit)给从设备 前8个clk 从设备不要影响SDA,从设备不驱动三极管 主设备决定数据,主设备要发送1时不驱动三极管,要发送0时驱动三极管 第9个clk,由从设备决定数据 主设备不驱动三极管 从设备决定数据,要发出回应信号的话,就驱动三极管让SDA变为0 从这里也可以知道ACK信号是低电平 从上面的例子,就可以知道怎样在一条线上实现双向传输,这就是SDA上要使用上拉电阻的原因。 5.6 为何SCL也要使用上拉电阻? 在第9个时钟之后,如果有某一方需要更多的时间来处理数据,它可以一直驱动三极管把SCL拉低。 当SCL为低电平时候,大家都不应该使用IIC总线,只有当SCL从低电平变为高电平的时候,IIC总线才能被使用。 当它就绪后,就可以不再驱动三极管,这是上拉电阻把SCL变为高电平,其他设备就可以继续使用I2C总线了。 对于IIC协议它只能规定怎么传输数据,数据是什么含义由从设备决定。 06 I2C通信的高级应用 在嵌入式系统设计中,I2C应用广泛,如: 6.1 传感器网络 在多传感器系统中,I2C用于读取各种环境参数,如温度、湿度、光照强度等。这些数据可以被用于自动化控制系统或数据监测。 6.2 多设备控制 在复杂的嵌入式系统中,如机器人或无人机,I2C用于控制和监测多个执行器和传感器,实现精确的运动控制和环境反馈。 6.3 嵌入式通信网络 I2C也常用于建立微控制器和各种外围设备(如显示屏、存储设备等)之间的通信网络。 本文章源自奇迹物联开源的物联网应用知识库Cellular IoT Wiki,更多技术干货欢迎关注收藏Wiki: Cellular IoT Wiki 知识库(https://rckrv97mzx.feishu.cn/wiki/wikcnBvAC9WOkEYG5CLqGwm6PHf) (如有侵权,联系删除)
  • 热度 2
    2024-9-30 15:52
    105 次阅读|
    0 个评论
    01 物联网系统中为什么要使用OLED屏 物联网系统中使用OLED屏的原因主要有以下几点: 显示效果的优越性 高对比度与色彩表现:OLED屏能够实现自发光,无需背光,因此能够展现更高的对比度和更深的黑色,使得画面更加鲜明逼真。同时,OLED屏能够提供更广阔的色域和更准确的色彩表现,使图像更加饱满、细腻,色彩更加真实。 视角宽广:OLED屏具有较大的视角范围,即使从不同角度观看,图像的亮度和色彩仍然保持稳定,不会出现颜色失真或亮度降低的情况。 响应速度快:OLED屏的像素点能够快速切换,响应速度更快,能够呈现更流畅的动态画面,减少运动模糊。 轻薄与灵活性 薄型轻便:OLED屏由于不需要背光模组,可以设计得更薄更轻,非常适合物联网设备中对于轻薄化的需求。这种特性使得OLED屏在智能手表、可穿戴设备等物联网设备中得到广泛应用。 柔性特性:OLED屏还具有柔性特性,可以弯曲和折叠,这为物联网设备的设计提供了更多的可能性。例如,在智能家居中,OLED屏可以嵌入到各种曲 面或不规则形状的设备中,提升设备的美观性和实用性。 低功耗与长续航 OLED屏在低功耗方面表现优异,因为它只在需要显示内容的像素点上发光,而不需要像传统LCD屏那样整个背光模组都保持开启状态。这种特性使得OLED屏在物联网设备中能够延长电池寿命,提升用户体验。 适应性与扩展性 多种应用场景:OLED屏的优异性能使得它适用于多种物联网应用场景,包括智能家居、可穿戴设备、工业自动化等。在智能家居中,OLED屏可以作为智能控制面板或家电显示屏;在可穿戴设备中,OLED屏可以呈现丰富的信息并提升设备的时尚感。 易于集成:OLED屏通常具有标准的接口和通信协议,易于与其他物联网设备或系统进行集成。这种集成能力使得在物联网系统中部署OLED屏变得更加简单快捷。 具体应用场景 OLED屏的应用场景非常广泛,包括但不限于以下几个领域: 商业展示:如透明屏展示窗口、广告宣传等。 博物馆和展览:用于展示文物、艺术品等,提供沉浸式的观展体验。 建筑和室内设计:应用于玻璃幕墙、楼梯扶手等,增强建筑的现代感和创意性。 汽车内部:如仪表盘、中控台和车载娱乐系统等,提升驾驶体验和乘车舒适度。 广告和展示活动:在户外广告牌、展览展示和活动现场中创造视觉冲击力。 舞台和演艺表演:用于舞台背景、舞美设计等,提升演出的震撼力和艺术感。 电视和显示屏:在家庭娱乐和专业领域中提供高对比度、广阔色域和快速响应时间的显示效果。 移动设备:如智能手机、平板电脑和可穿戴设备等,轻薄柔软、功耗低且亮度高。 综上所述,物联网系统中使用OLED屏的原因主要包括其显示效果的优越性、轻薄与灵活性、低功耗与长续航以及适应性与扩展性等方面。这些优势使得OLED屏成为物联网设备中不可或缺的重要组成部分。 本文会再为大家详解显示屏家族中的一员——OLED屏。 02 OLED的屏定义 OLED(Organic Light-Emitting Diode)即有机发光二极管,是一种采用有机材料制成的显示屏幕。它无需背光灯,通过非常薄的有机材料涂层和玻璃基板(或柔性有机基板),在电流通过时,这些有机材料会自行发光,从而实现图像的显示。OLED屏幕被誉为“梦幻显示器”,是第三代显示技术的代表。 03 OLED屏的原理 OLED的显示原理与LCD有着本质上的区别。它主要通过电场驱动,使有机半导体材料和发光材料在过载流子注入和复合后实现发光。具体来说,OLED屏幕以ITO玻璃透明电极作为器件阳极,金属电极作为阴极,通过电源驱动,将电子从阴极传输到电子传输层,空穴从阳极注入到空穴传输层。电子和空穴在发光层中相遇后形成激子,激子使发光分子激发,经过辐射后产生光源。简单来说,一块OLED屏幕就是由无数个“小灯泡”组成。 04 OLED屏分类 根据使用有机功能材料的不同,OLED器件可以分为两大类:小分子器件和高分子器件(也称为PLED)。根据驱动方式的不同,OLED器件又可以分为无源驱动型(Passive Matrix, PM)和有源驱动型(Active Matrix, AM)。此外,还有透明OLED、顶部发光OLED、可折叠OLED、白光OLED等多种类型,每种OLED都有其独特的用途和优势。 05 OLED屏选型参数 在选型OLED屏时,需要考虑以下参数: 屏幕尺寸与分辨率:根据实际应用场景选择合适的屏幕尺寸和分辨率,以确保图像的清晰度和显示效果。 亮度与对比度:OLED屏具有高亮度和高对比度的特点,但具体数值仍需根据实际需求进行选择。 刷新率:刷新率越高,画面越流畅,需要根据应用场景选择合适的刷新率。 能耗:OLED屏具有较低的能耗,但不同型号的能耗可能存在差异,需根据实际应用场景和需求进行选择。 透明度:对于透明OLED屏,透明度是一个重要的选型参数,需要根据实际需求选择合适的透明度。 06 OLED屏使用注意事项 在使用OLED屏时,需要注意以下事项: 防止长时间显示静态图像:OLED屏容易出现“烧屏”问题,因此需避免长时间显示静态图像或在同一位置显示相同图像。 合理控制屏幕亮度:过高的亮度会加速像素灯的老化,需根据实际情况合理控制屏幕亮度。 避免频繁开关机:频繁的开关机操作会对OLED屏产生冲击,影响其使用寿命,需合理安排开关机时间。 避免长时间曝光:OLED屏在长时间曝光下易出现像素灯老化,需尽量避免长时间暴露在阳光下或其他强光源下。 定期清洁屏幕:使用专用的屏幕清洁剂和无纺布定期清洁屏幕,避免硬物刮伤屏幕。 注意适宜的温湿度:OLED屏对环境温度和湿度有一定要求,需放置在温度适宜、湿度适中的环境中。 07 OLED屏厂商 目前,OLED技术的主要厂商包括三星、LG等国际巨头,他们在OLED领域具有领先的技术和市场份额。同时,我国也有多家厂商在不断加强对OLED屏幕的研究和生产,如华星光电、京东方、天马科技等,这些厂商在OLED技术上不断取得突破,并成功将OLED产品应用到各类设备中。 供应商A:郑州中景园电子技术有限公司 1、产品能力 (1)选型手册 (2)主推型号1:0.96英寸OLED显示屏 对应的产品详情介绍 尺 寸 0.96英寸OLED 材 料 PM OLED 分 辨 率 128*64 接口类型 IIC接口 管脚数量 4 控制芯片 SSD1306 显示颜色 白色,蓝色,黄蓝双色三种,根据需要选择 显示区域 21.744 × 10.864 (mm) 模组尺寸 27.3x27.8 (mm) 像素间距 0.17 × 0.17 (mm) 像素尺寸 0.154 × 0.154 (mm) 配套裸屏 0.96老款插接式30PIN短排线(G39) 工作电压 3.3V 视角方向 全视角 工作温度 -40~70度 白色型号 ZJY096I0400WG03 蓝色型号 ZJY096I0400BG03 双色型号 ZJY096I0400YBG03 硬件参考设计 (如有侵权,联系删除) 本文章源自奇迹物联开源的物联网应用知识库Cellular IoT Wiki,更多技术干货欢迎关注收藏Wiki: Cellular IoT Wiki 知识库(https://rckrv97mzx.feishu.cn/wiki/wikcnBvAC9WOkEYG5CLqGwm6PHf)
  • 2024-9-30 14:42
    114 次阅读|
    0 个评论
    01 物联网系统中为什么要使用IIC I/O扩展芯片 在物联网系统中使用IIC(也称为I2C)I/O扩展芯片的原因主要可以归结为以下几点: 1、扩大I/O端口数量 硬件资源限制:许多微控制器(MCU)自带的I/O端口数量有限,但物联网项目往往需要连接大量的传感器、执行器和其他外设。I2C I/O扩展芯片能够通过I2C接口提供额外的I/O端口,从而满足项目需求。 灵活性:通过扩展芯片,设计者可以根据项目需求灵活增加或减少外设连接,而无需更换主控制器。 2、简化布线和连接 减少引脚需求:I2C总线仅需要两根线(SCL时钟线和SDA数据线)即可实现多个设备之间的通信,这大大减少了系统布线的复杂性和成本。 便于管理:通过I2C总线连接的设备可以使用统一的通信协议进行通信,简化了系统的维护和管理工作。 3、提高系统性能 高效通信:I2C协议具有高效的通信机制,能够支持高速数据传输,提高系统的整体性能。 时钟控制:I2C I/O扩展芯片可以控制外部设备的时钟,有助于优化系统的时序控制,提高系统的稳定性和可靠性。 4、降低成本和功耗 减少元件数量:使用I2C I/O扩展芯片可以减少系统中其他类型扩展元件的使用,从而降低整体成本。 低功耗:I2C总线支持低功耗模式,有助于降低系统的整体功耗,延长电池寿命(在便携式或无线物联网设备中尤为重要)。 5、广泛应用场景 传感器网络 :在物联网系统中,传感器是不可或缺的组成部分。I2C I/O扩展芯片可以方便地连接各种类型的传感器(如温度、湿度、加速度计等),实现数据的采集和传输。 工业自动化 :在工业自动化领域,PLC等控制系统需要与大量外设进行通信。I2C I/O扩展芯片能够满足这一需求,提高工业自动化系统的集成度和可靠性。 智能家居 :智能家居系统需要连接和控制各种智能家电和设备。I2C I/O扩展芯片可以扩展智能家居系统的控制能力,实现更多样化的功能。 工业自动化和控制 :在工业自动化系统中,经常需要连接和控制大量的传感器、执行器和其他设备。IIC IO扩展芯片可以提供更多的IO端口,以便与这些设备进行通信和控制。例如,它们可以用于读取传感器的数据、控制执行器的动作或与其他工业设备进行交互。 消费电子产品 :在消费电子产品中,如智能家居设备、智能音箱、智能手环等,IIC IO扩展芯片也发挥着重要作用。它们可以用于扩展设备的IO接口,以便连接更多的外部设备或传感器。例如,智能音箱可以通过IIC IO扩展芯片连接更多的麦克风或扬声器,提高音频处理的性能和质量。 嵌入式系统 :在嵌入式系统中,由于资源限制和成本考虑,微控制器的IO端口数量往往有限。IIC IO扩展芯片可以有效地解决这个问题,为嵌入式系统提供额外的IO端口。这些端口可以用于连接外部设备、扩展系统功能或实现与其他系统的通信。 测试与测量设备 :在测试和测量领域,IIC IO扩展芯片可以用于连接各种测试设备和传感器。通过扩展IO端口,测试设备可以获取更多的数据输入,提高测试的准确性和效率。同时,IIC IO扩展芯片还可以用于控制测试过程中的各种设备和参数。 医疗设备 :在医疗设备中,IIC IO扩展芯片可以用于连接各种医疗传感器和执行器。例如,在监护仪中,IIC IO扩展芯片可以连接多个生命体征传感器,实时监测患者的血压、心率等生理参数。在医疗机器人中,IIC IO扩展芯片可以控制机器人的运动和操作。 综上所述, IIC(I2C)I/O扩展芯片在物联网系统中扮演着重要角色,它们通过提供额外的I/O端口、简化布线和连接、提高系统性能、降低成本和功耗以及支持广泛应用场景等方式,为物联网系统的设计和实现带来了诸多便利和优势。 02 IIC I/O扩展芯片的行业知识介绍 IIC I/O扩展芯片定义 IIc接口IO扩展芯片是一种可以为电子设备添加输入输出功能的集成电路,它可以有效提高设备的性能。 IIC 原理 I²C通信协议 :I²C是一种串行通信协议,用于连接微控制器及其外围设备。它只需要两根线(SDA数据线和SCL时钟线)即可实现全双工通信,同时还可以连接多个设备到同一总线上。 设备寻址 :每个I²C设备都有一个唯一的地址,主控制器通过发送设备的地址来选中并与之通信。I²C扩展IO芯片也具备一个或多个可设置的地址,以便主控制器能够识别并与之通信。 数据传输 :在选中特定的I²C设备后,主控制器可以通过I²C总线发送数据到该设备,或者从该设备读取数据。对于I²C扩展IO芯片,主控制器可以发送命令来设置或读取IO端口的电平状态,从而实现对外围设备的控制或监测。 中断机制 :许多I²C扩展IO芯片还具备中断功能。当IO端口的电平状态发生变化时,芯片可以产生一个中断信号,通知主控制器进行相应的处理。这样,主控制器可以更加高效地响应外部事件,而无需持续轮询IO端口的状态。 内部寄存器 :I²C扩展IO芯片内部通常包含多个寄存器,用于存储IO端口的配置信息、数据等。主控制器可以通过I²C总线访问这些寄存器,以实现对IO端口的控制和管理。 IIC I/O扩展芯片的参数选型 电源电压 :确保你的系统电源电压与IO扩展芯片的电源电压兼容。常见的电源电压范围包括1.8V、3.3V、5V等。 IO口数量 :根据你的应用需求确定所需的IO口数量。有些IO扩展芯片提供8个、16个或更多的IO口。 IO口类型 :考虑你需要的IO口类型,如准双向口、开漏输出等。准双向口既可以作为输入也可以作为输出,而开漏输出则可以用于驱动LED等外部设备。 通信接口 :确保IO扩展芯片支持你的主控制器所使用的通信接口,如I²C、SPI等。I²C接口通常使用两根线(SDA和SCL)进行通信,具有简单易用、支持多设备连接等优点。 中断功能 :如果你的应用需要实时监测外部设备状态的变化,那么选择具有中断功能的IO扩展芯片将非常有用。当中断引脚被触发时,主控制器可以立即响应并处理相应的事件。 封装和尺寸 :根据你的应用需求和电路板空间限制选择合适的封装和尺寸。常见的封装类型包括TSSOP、QFN、SSOP等,尺寸范围也有所不同。 工作温度和功耗 :考虑你的应用所在的环境温度和功耗要求。选择具有适当工作温度和低功耗的IO扩展芯片将有助于提高系统的稳定性和可靠性。 其他特性 :根据你的应用需求,还可以考虑其他特性,如驱动能力(最大输出电流)、输入/输出电平范围、可编程性(是否支持通过编程来配置IO口)等。 IIC I/O扩展芯片的厂商 NXP Semiconductors(恩智浦半导体) :NXP是全球领先的半导体公司之一,提供广泛的IIC IO扩展芯片产品。其产品线包括PCF8574、PCA9555等,这些芯片具有高性能、低功耗和易用性等特点,广泛应用于各种领域。 Microchip Technology(微芯科技) :Microchip是一家专注于嵌入式控制和混合信号半导体解决方案的供应商。其IIC IO扩展芯片产品包括MCP23017、MCP23S17等,这些芯片具有高集成度、高可靠性和低功耗等特点,适用于工业自动化、消费电子和汽车电子等领域。 Texas Instruments(德州仪器) :德州仪器是一家全球性的半导体设计和制造公司,其IIC IO扩展芯片产品包括TPS2113、TPS2296等。这些芯片具有高性能、高精度和低功耗等特点,广泛应用于电源管理、电机控制和工业自动化等领域。 STMicroelectronics(意法半导体) :意法半导体是一家全球领先的半导体公司,提供广泛的IIC IO扩展芯片产品。其产品线包括VLSI Solution的82C55A等,这些芯片具有高速、高可靠性和低功耗等特点,适用于各种嵌入式系统和工业自动化应用。 此外,还有其他一些厂商也提供IIC IO扩展芯片产品,如Maxim Integrated、Renesas Electronics等。在选择IIC IO扩展芯片时,需要根据具体的应用需求和性能要求来选择合适的厂商和产品。国内也有一些知名的IIC(实际上是I²C,即Inter-Integrated Circuit)IO扩展芯片厂商,以下是一些例子: 中微爱芯 :作为中国领先的半导体公司之一,中微爱芯专注于消费电子芯片和小家电芯片的研发。他们的产品可能包括IIC IO扩展芯片,但具体型号和规格可能需要查询其官方渠道。 紫光展锐 :紫光展锐是中国集成电路设计和解决方案供应商,他们在移动通信、数字多媒体等领域积累了丰富的经验。虽然他们主要以手机芯片闻名,但他们的产品线可能也涵盖IIC IO扩展芯片。 华大半导体 :华大半导体是中国电子信息产业集团有限公司(CEC)旗下的核心企业,是中国“909”工程集成电路项目的重点承担单位。他们提供多种类型的半导体产品,包括可能包含IIC IO扩展芯片的产品。 03 IIC I/O扩展芯片的硬件设计方案 本文主要采用了信路达的XL9535做为驱动芯片。 1、XL9535概述 XL9535是一款24引脚CMOS器件,通过I²C总线/SMBus接口 串行时钟线(SCL)、串行数据(SDA)为大多数微控制器系列提供16位通用并行输入/输出(GPIO)扩展。这些设备设计用于2.3-V至5.5-V Vcc操作。这些改进包括更高的驱动能力、5V I/O容差、更低的电源电流、单独的I/O配置和更小的封装。 通电时,I/O被配置为输入。系统主机可以通过写入I/O配置位来启用I/O作为输入或输出。每个输入或输出的数据都保存在相应的输入或输出寄存器中。输入端口寄存器的极性可以用极性反转寄存器反转。所有寄存器均可由系统主控器读取。 当任何输入状态与其对应的输入端口寄存器状态不同时,这些设备开漏中断(/INT)输出被激活,并用于向系统主机指示输入状态已改变。在超时或其他不当操作的情况下,系统主设备可以利用上电重置功能重置这些设备。通电重置将寄存器设置为默认值,并初始化I²C/SMBus状态机。三个硬件引脚(A0、A1和A2)改变固定的I²C总线地址, 并允许多达八个设备共享同一I²C总线/SMBus。 2、 XL9535特性 I²C总线至16位GPIO扩展器 工作电源电压范围为2.3 V至5.5 V 低待机电流消耗 5 V容错I/O端口 400 kHz快速模式I²C总线时钟频率 SCL/SDA输入上的噪声滤波器 内部通电复位 通电时无故障 极性反转寄存器 开漏有源低中断输出 16个I/O引脚,默认为16个输入 3、XL9535引脚配置和功能 4、XL9535功能框图和参考设计 04 IIC I/O扩展芯片的软件设计方案 本文采用了奇迹物联的红豆版开源技术平台为主控单元,一步步手把手教会读者如何使用红豆版开源平台编写XL9535驱动。这里对代码就不多详解,如果需要详细了解,请到奇迹物联的红豆版开源平台了解详细代码讲解。 1 Gitee链接地址 Demo位于amaziot_bloom_os_sdk\sample\3rd\1.0_XL9535 Gitee源码地址:https://gitee.com/ning./hongdou Github源码地址:https://github.com/ayumid/hongdou 编译指令:.\build.bat -l .\amaziot_bloom_os_sdk\sample\3rd\1.0_XL9535 2 组件功能介绍 驱动XL9535芯片,实现输入IO检测。 3 代码讲解 1 drv_xl9535_i2c_init 功能:该函数用于,将发送数据长度写入xl9535寄存器。 参数:五 返回值:无 示例: C //初始化i2c总线 ret = drv_xl9535_i2c_init(); 2 drv_xl9535_i2c_read 功能:该函数用于,读取I2C从机。 参数:五 返回值:无 示例: C RegAddr = 0x00; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 00 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); 3 drv_xl9535_i2c_write 功能:该函数用于,发送数据到I2C从机。 参数:五 返回值:无 示例: C RegAddr = 0x06; RegWriteValue = 0xFF; ret = drv_xl9535_i2c_write(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("write i2c value=0x%x, ret=%d\n", RegWriteValue, ret); 4 Demo实战 4.1 概述 上电后,按下按键,串口会打印出按下了哪一个按键 4.2 测试 测试步骤: 参考编译教程,和文档开头的编译指令,进行编译 按照编译教程选择对应的选项 烧录 4.3 宏定义介绍 sample_xl9535_uart_printf 输出日志到DEBUG 串口,日志比较少,可以输出到这个串口,如果日志比较多,需要输出到usb口,以免不必要的问题出现 sample_xl9535_catstudio_printf 输出日志到USB 串口,使用catstudio查看,catstudio查看日志需要更新对应版本mdb.txt文件,软件打开filtter过滤日志,只查看用户输出的日志 SAMPLE_XL9535_STACK_SIZE 栈空间宏定义 4.4 全局变量介绍 sample_xl9535_int_detect_stack_ptr 任务栈空间,本例使用数组实现,用户在做项目时,可以预先估算下当先任务需要的大致栈空间,OS没有提供可以查看栈空间使用情况的API sample_xl9535_int_detect_task_ref 任务指针 4.5 函数介绍 Phase1Inits_enter 底层初始化,本例空 Phase1Inits_exit 底层初始化,本例空 Phase2Inits_enter 底层初始化,本例空 Phase2Inits_exit 创建主任务,初始化INT 引脚 代码片段: C int ret = 0; GPIOConfiguration config = {0}; //创建定时器 OSATimerCreate(&sample_xl9535_int_detect_timer_ref); //创建中断事件 OSAFlagCreate( &sample_xl9535_int_detect_flg_ref); Os_Create_HISR(&sample_xl9535_int_detect_hisr, "sample_xl9535_int_detect_hisr", sample_xl9535_detect_handler, 2); //创建中断处理任务 OSATaskCreate(&sample_xl9535_int_detect_task_ref, sample_xl9535_int_detect_stack_ptr, SAMPLE_XL9535_STACK_SIZE, 100, "detect_task", sample_xl9535_detect_task, NULL); //初始化int引脚,这里使用70脚 gpio126 config.pinDir = GPIO_IN_PIN; config.pinEd = GPIO_RISE_EDGE; config.pinPull = GPIO_PULLUP_ENABLE; config.isr = sample_xl9535_irq_handler; GpioInitConfiguration(SAMPLE_GPIO_ISR_PIN_NUM, config); sample_xl9535_detect_task 主任务,代码发分为两部分,一部分是发送不定长数据;另一部分是上电后等待其它模块发送的数据,收到后打印到串口。 代码片段: C void sample_xl9535_detect_task(void *param) { GPIO_ReturnCode ret = 0; UINT32 value = 0; OSA_STATUS status = OS_SUCCESS; UINT32 flag_value = 0; unsigned char RegAddr = 0; unsigned char RegWriteValue = 0; unsigned char RegReadValue0 = 0; unsigned char RegReadValue1 = 0; UINT8 down_en = 0; UINT8 up_en = 0; UINT8 sig_en = 0; UINT8 mul_en = 0; UINT8 read_en = 0; UINT8 de_en = 0; UINT8 ck_en = 0; UINT8 sim_en = 0; UINT8 gnss_en = 0; UINT8 nb_en = 0; UINT8 fourg_en = 0; //初始化i2c总线 ret = drv_xl9535_i2c_init(); sample_xl9535_catstudio_printf("ql_i2c_init ret %d", ret); //上电后,读xl9535 io扩展芯片的8个寄存器,测试用 RegAddr = 0x00; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 00 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x01; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 01 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x02; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 02 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x03; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 03 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x04; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 04 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x05; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 05 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x06; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 06 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x07; ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("read reg 07 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); //等1s OSATaskSleep(1 * 200); //初始化xl0535 io扩展芯片的06 07寄存器,设置所有的io都是输入 RegAddr = 0x06; RegWriteValue = 0xFF; ret = drv_xl9535_i2c_write(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("write i2c value=0x%x, ret=%d\n", RegWriteValue, ret); RegAddr = 0x07; RegWriteValue = 0xFF; ret = drv_xl9535_i2c_write(&RegAddr, &RegReadValue0); sample_xl9535_catstudio_printf("write i2c value=0x%x, ret=%d\n", RegWriteValue, ret); while(1) { // sample_xl9535_catstudio_printf("sample_xl9535_detect_task\r\n"); status = OSAFlagWait(sample_xl9535_int_detect_flg_ref, 0x01, OSA_FLAG_OR_CLEAR, &flag_value, OSA_SUSPEND); //红豆版,设置了边沿,但是还是会上升沿,下降沿都触发,所以会执行两次 if(flag_value & 0x01) { RegAddr = 0x00; //ret = ql_i2c_read(i2c_no, SAMPLE_I2C_SLAVE_ADDR, RegAddr, &RegReadValue0, 1); ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue0); // sample_xl9535_catstudio_printf("read reg 00 i2c value=0x%x, ret=%d\n", RegReadValue0, ret); RegAddr = 0x01; //ret = ql_i2c_read(i2c_no, SAMPLE_I2C_SLAVE_ADDR, RegAddr, &RegReadValue0, 1); ret = drv_xl9535_i2c_read(&RegAddr, &RegReadValue1); // sample_xl9535_catstudio_printf("read reg 01 i2c value=0x%x, ret=%d\n", RegReadValue1, ret); //寄存器值非默认值,表示有io输入,否则表示是上升沿中断 if(RegReadValue0 != 0xff || RegReadValue1 != 0x07) { event_ticks = OSAGetTicks(); int_status = 0; //判断是那个IO这里使用寄存器值判断,后续可以改为根据位判断 if(RegReadValue1 == DRV_XL9535_BUTTON_DOWN_EN) { // sample_xl9535_catstudio_printf("down int"); down_en = 1; } else if(RegReadValue1 == DRV_XL9535_BUTTON_UP_EN) { // sample_xl9535_catstudio_printf("up int"); up_en = 1; } else if(RegReadValue1 == DRV_XL9535_BUTTON_SIG_EN) { // sample_xl9535_catstudio_printf("sig int"); sig_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_MUL_EN) { // sample_xl9535_catstudio_printf("mul int"); mul_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_READ_EN) { // sample_xl9535_catstudio_printf("read int"); read_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_DE_EN) { // sample_xl9535_catstudio_printf("de int"); de_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_CK_EN) { // sample_xl9535_catstudio_printf("ck int"); ck_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_SIM_EN) { // sample_xl9535_catstudio_printf("sim int"); sim_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_GNSS_EN) { // sample_xl9535_catstudio_printf("gnss int"); gnss_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_NB_EN) { // sample_xl9535_catstudio_printf("nb int"); nb_en = 1; } else if(RegReadValue0 == DRV_XL9535_BUTTON_4G_EN) { // sample_xl9535_catstudio_printf("4g int"); fourg_en = 1; } } else { if(int_status == 0) { UINT32 keep_ticks = OSAGetTicks() - event_ticks; 20) { //检测到某个IO被按下后,可以在这里,或者发送信号,消息等去其它任务执行相应操作 // sample_xl9535_catstudio_printf("keep_ticks:%d, event_ticks:%d", keep_ticks, event_ticks); if(down_en == 1) { sample_xl9535_catstudio_printf("down en"); } else if(up_en == 1) { sample_xl9535_catstudio_printf("up en"); } else if(sig_en == 1) { sample_xl9535_catstudio_printf("sig en"); } else if(mul_en == 1) { sample_xl9535_catstudio_printf("mul en"); } else if(read_en == 1) { sample_xl9535_catstudio_printf("read en"); } else if(de_en == 1) { sample_xl9535_catstudio_printf("de en"); } else if(ck_en == 1) { sample_xl9535_catstudio_printf("ck en"); } else if(sim_en == 1) { sample_xl9535_catstudio_printf("sim en"); } else if(gnss_en == 1) { sample_xl9535_catstudio_printf("gnss en"); } else if(nb_en == 1) { sample_xl9535_catstudio_printf("nb en"); } else if(fourg_en == 1) { sample_xl9535_catstudio_printf("4g en"); } } down_en = 0; up_en = 0; sig_en = 0; mul_en = 0; read_en = 0; de_en = 0; ck_en = 0; sim_en = 0; gnss_en = 0; nb_en = 0; fourg_en = 0; } int_status = 1; } } } } 4.6 固件 上电后,按下按键,串口会打印出按下了哪一个按键 注:本文部分内容来源于网络,如有侵权,请及时联系我们。 本文章源自奇迹物联开源的物联网应用知识库Cellular IoT Wiki,更多技术干货欢迎关注收藏Wiki: Cellular IoT Wiki 知识库(https://rckrv97mzx.feishu.cn/wiki/wikcnBvAC9WOkEYG5CLqGwm6PHf)
  • 2024-9-30 14:38
    103 次阅读|
    0 个评论
    01 概述 IO输入检测是嵌入式开发场景中,经常会遇到的情况,按键检测,外部IC中断检测,都需要输入检测。 输入信号的检测的前提条件,那就是需要在读取信号前,将输入信号的IO端口进行置1,然后就可以读取该端口上的状态,由于我们的单片机是带上拉电阻的,所以当我们把端口置1后,端口上如果什么也不接,就是处于悬空状态,那么我们读到的就是1,也就是说,单片机的常态是1,要想检测到外界的信号变化,就需要对端口置0,我们的外电路的输入信号应该是接地的才会有效。 02 按键的消抖问题 按键是机械的, 在按下和松手的瞬间会伴随有一定时间的抖动,按键开关不会马上稳定接通或一下断开,使用按键时会产生图中所示波浪信号,我们写程序的时候需要用软件消抖处理滤波。 当然也可以设计硬件进行消抖,如果有硬件消抖,软件就不用再设计消抖处理。硬件消抖一般是利用RC电路的电容充放电特性来对抖动产生的电压毛刺进行滤波,简单示意图如图 03 检测IC消抖 大多数IC的输出INT接口,不需要消抖。具体可以使用示波器,查看当外部IC的INT信号发生时,是否会产生抖动。 如果需要去抖,可以参考按键去抖电路,硬件消抖,或者世界在软件上消抖。但如果外部IC产生的INT的信号频率很高,注意软件消抖的延时问题。 04 组件的使用 1 Gitee链接地址 Demo位于amaziot_bloom_os_sdk\sample\3rd\3.2_MUL_MENU-SSD1315 Gitee源码地址:https://gitee.com/ning./hongdou Github源码地址:https://github.com/ayumid/hongdou 编译指令:.\build.bat -l .\amaziot_bloom_os_sdk\sample\3rd\3.2_MUL_MENU-SSD1315 2 组件功能介绍 INPUT组件适用于所有需要数据检测的场景,例如按键,中断等。组件中实现了4路输出检测,如需增加更多,可以参考组件中的代码增加。 该组件配合MENU组件使用,实现了按键多级菜单。客户可以根据自己的场景进行微改。 3 代码讲解 4路代码重复性较高,所以这里只列出第X路的API 1 input_keyX_irq_hadler 功能:该函数用于,中断回调函数。 参数:无 返回值:无 示例: C config.isr = input_key0_irq_hadler; 2 input_keyX_hadler 功能:该函数用于,回调函数。 参数:无 返回值:无 示例: C Os_Create_HISR(&input_key0_hisr, "Gpio_Hisr", input_key0_hadler, 2); 3 input_keyX_callback 功能:该函数用于,去抖定时器回调函数。 参数:无 返回值:无 示例: C OSATimerStart(input_key0_timer_ref, 2, 0, input_key0_callback, 0); 4 input_key_init 功能:该函数用于,初始化菜单按键使用的gpio。 参数:无 返回值:无 示例: C //初始化按键 input_key_init(); 返回值:无 4 Demo实战 该组件需要和MENU配合实验,实战内容和MENU中一样,不重复增加Demo。 4.1 创建一个Demo 复制3.1_SSD1315示例工程,到同一个文件夹下,修改文件名为3.2_MUL_MENU-SSD1315,如图: 4.2 修改makefile 增加文件组件所在目录头文件路径,和源文件路径,如图: 4.3 增加头文件 使用代码编辑器,将新建的工程文件加入代码编辑器中,打开main.c,修改main.c,加入am.h等头文件,如图: 4.4 修改代码 在Phase2Inits_exit 创建一个任务,如图: 4.5 宏定义介绍 sample_ssd1315_uart_printf 输出日志到DEBUG 串口,日志比较少,可以输出到这个串口,如果日志比较多,需要输出到usb口,以免不必要的问题出现 sample_ssd1315_catstudio_printf 输出日志到USB 串口,使用catstudio查看,catstudio查看日志需要更新对应版本mdb.txt文件,软件打开filtter过滤日志,只查看用户输出的日志 SAMPLE_SSD1315_STACK_SIZE 栈空间宏定义 4.6 全局变量介绍 sample_ssd1315_stack_ptr 任务栈空间,本例使用数组实现,用户在做项目时,可以预先估算下当先任务需要的大致栈空间,OS没有提供可以查看栈空间使用情况的API sample_ssd1315_task_ref 任务指针 4.7 函数介绍 Phase1Inits_enter 底层初始化,本例空 Phase1Inits_exit 底层初始化,本例空 Phase2Inits_enter 底层初始化,本例空 Phase2Inits_exit 创建主任务,初始化消息队列,定时器,任务等。 代码片段: C int ret = 0; GPIOConfiguration config = {0}; //创建定时�? OSATimerCreate(&sample_xl9535_int_detect_timer_ref); //创建中断处理任务 OSATaskCreate(&sample_ssd1315_task_ref, sample_ssd1315_stack_ptr, SAMPLE_SSD1315_STACK_SIZE, 100, "ssd1315_task", sample_ssd1315_task, NULL); input_run_main_menu 显示主界面,用户任务中调用这个函数。 代码片段: C static input_OPTION_TYPE_T input_OptionList = { {"TOOLS1", NULL}, {"TOOLS2", NULL}, {"TOOLS3", NULL}, {"TOOLS4", NULL}, {"TOOLS5", NULL}, {".."}}; static input_HANDLE_TYPE_T MENU = {.OptionList = input_OptionList}; input_run_menu(&MENU); input_run_tools_menu 根据按键选择运行工具界面。 代码片段: C static input_OPTION_TYPE_T input_OptionList SDK_CUST_SKU : SDK_PS_MODE : SDK_CHIP_VER : SDK_OS_TYPE : Platform Convertion Tools v4.01 with PS option extension Convertion done! |INPUT |out\bin\cp_1606L.bin |MARK |NAME |EXEADDR .LOADADDR.LENGTH .CPZLADDR|COMPRESS STASTIC | |--------|--------|--------.--------.--------.--------|------------------------------| |This Is LteOnly 4M| 00003000 |This Is LteOnly 4M| 00001000 |This Is LteOnly 4M| 0000a000 |This Is LteOnly 4M| 0001e000 |This Is LteOnly 4M| 0001b000 |This Is LteOnly 4M| 0001b000 |This Is LteOnly 4M| 0001a000 |This Is LteOnly 4M| 0001a000 |This Is LteOnly 4M| 00011000 |This Is LteOnly 4M| 0001e000 |This Is LteOnly 4M| 00021000 |This Is LteOnly 4M| 00012000 |--------|--------|--------.--------.--------.--------|------------------------------| 0x0014a000| 1.289(MB)| |------------------------------------------------------------------------------------| cp_1606L.axf cp_1606L.bin cp_1606L.map gnumake: Leaving directory `F:/3.asr-b/cat.1-asr1606/1.software/BlOOM_OS_1606_OPENCPU_1191_A09_WIHT_NEWRF/amaziot_bloom_os_sdk/sample/3rd/3.2_MUL_MENU-SSD1315' "copy NEZHAC_CP_CNR_MIFI_TX.bin to ./ " 已复制 1 个文件。 4.9 生成固件 参考入门中开发工具,生成工具。 4.10 测试 测试步骤: 参考编译教程,和文档开头的编译指令,进行编译 按照编译教程选择对应的选项 烧录 4.11 固件 上电后,屏幕会显示主菜单,通过4个按键:选择,返回,上,下。可以选择不同的菜单; 点击下载 OLED 多级菜单 Demo固件 5 生态组件链接 注:本文部分内容来源于网络,如有侵权,请及时联系我们。 本文章源自奇迹物联开源的物联网应用知识库Cellular IoT Wiki,更多技术干货欢迎关注收藏Wiki: Cellular IoT Wiki 知识库(https://rckrv97mzx.feishu.cn/wiki/wikcnBvAC9WOkEYG5CLqGwm6PHf)
  • 热度 3
    2024-9-30 14:37
    112 次阅读|
    0 个评论
    01 概述 导航和可查找性是用户体验设计的核心方面。如果用户无法到达他们想去的地方或找到他们需要的东西,他们会感到沮丧,并可能决定去别处寻找。在具有特别复杂的信息架构的网站上, 多级菜单是改善导航和可查找性的有效方法,从而提供高效的Web体验,从而促进产品信任度和转化率。 由于产品及其使用设备的差异很大,因此没有一种万能的解决方案可以产生完美的多级菜单。但是,有一些经验法则可以帮助您构建多级菜单,以增强任何屏幕尺寸的导航和可查找性。 02 多级菜单的程序设计 1 循环方式   循环方式的设计思路:预先定义一个包含6个结构元素的结构体、5个字符型和1个指针型。第1个字符变量存放当前界面的索引号;第2个字符变量存放按下“down(向下)”键时需要跳转到的索引号;第3个字符变量存放按下“up(向上)”键时需要跳转到的索引号;第4个字符变量存放按下“enter(进入)”键时需要跳转的索引号;第5个字符变量存放按下“esc(退出)”键时需要跳转的索引号;第6个变量为函数指针变量,存放当前索引号下需要执行的函数的入口地址。 将所有需要显示的界面其所对应的执行函数和按键索引号以结构体的形式列表存储。具体实现如下: typedef struct{ uchar down_index; uchar up_index; uchar enter_index; uchar esc_index; void (*operate)(); }Key_index_struct;   假设菜单分3级,共10个界面,则有: Key_index_struct const Key_tab ={ {0, 0, 0, 1, 0,(*main_menu)}, {1, 2, 3, 4, 0,(*sub_menu1)}, {2, 3, 1, 5, 0,(*sub_menu2)}, {3, 1, 2, 7, 0,(*sub_menu3)}, {4, 4, 4, 4, 1,(*sub_menu1_1)}, {5, 6, 6, 5, 2,(*sub_menu2_1)}, {6, 5, 5, 5, 2,(*sub_menu2_2)}, {7, 8, 9, 7, 3,(*sub_menu3_1)}, {8, 9, 7, 8, 3,(*sub_menu3_2)}, {9, 7, 8, 9, 3,(*sub_menu3_3)}, }; void Lcd_display(void){ switch(Key_status){ case enter: Key_fun=Key_tab .enter_index; break; case down: Key_fun=Key_tab .down_index; break; case up: Key_fun=Key_tab .up_index; break; case esc: Key_fun=Key_tab .esc_index; break; default: return; break; } Key_fun_Pt=Key_tab .operate; (*Key_fun_Pt)();//执行当前按键的操作 }   当微处理器扫描键盘检测到有按键按下时,根据按键按下的类型,返回在当前界面下其所对应的跳转索引号,并执行相应的函数。   由于每个界面的绘制都是由一个独立函数实现的,从循环方式的实现过程中发现,每发生一次按键按下操作都需要重新绘制整个屏幕。如果核心处理器是低速主频的处理器,在界面切换的时候会闪烁。而且,每一个界面都有固定不变的索引号,在增加或删除界面的时候需要重新修改整个列表,降低了程序的可移植性。 2 查询方式   查询方式是通过结构体对自身的递归调用实现菜单的多级嵌套。   结构体通过对自身的两次调用构建双向列表。一个菜单界面即为一个节点,节点的前驱和后继分别存放其父节点和子节点的入口地址。 3 状态机方式   状态机是由事件驱动,在各个状态之间跳转。采用状态机方式时,只需要提供驱动事件(在此设计中驱动事件为有效的按键按下),然后根据按键扫描返回的键值,决定所要跳转的下一状态。   如图3所示,系统启动初始化是显示Main_menu界面,当按键检测有返回值(即有按键按下)时,根据按下的按键所代表的操作跳转到指定的状态。例如:按下Up或者是down键时,只是在Main_menu界面内高亮显示不同区域;按下Enter时,则要根据原来按下的Up和down键来选择需要跳转的方向,假设在按下Enter之前仅按下一次down键,则key_v的值为2(key_v的值默认为1,即默认选中子菜单的第一项),就跳转为Sub_menu2界面;按下Esc键时,为从子菜单返回到上一级菜单,如果已经是主菜单了则返回的还是主菜单。 由于使用的是状态机的方式,只有发生一次有效的按键,状态才会发生一次跳转。而且,仅当Enter和Esc键按下时,才会切换界面。所以即便是在高速CPU应用中,也不会出现屏幕闪烁的效果。 从图3中可以看出,当要发生状态跳转时,目的状态只能是当前状态几个分支预测中的一个,从而不需要遍历整个列表,能够适应高速数据处理的场合。 多级菜单的程序流程如图4所示。系统上电初始化后显示主菜单,键盘扫描可以通过主程序中循环查询或者中断扫描来实现,最终根据键盘返回的键值选择下一状态。 03 组件的使用 1 Gitee链接地址 Demo位于amaziot_bloom_os_sdk\sample\3rd\3.2_MUL_MENU-SSD1315 Gitee源码地址:https://gitee.com/ning./hongdou Github源码地址:https://github.com/ayumid/hongdou 编译指令:.\build.bat -l .\amaziot_bloom_os_sdk\sample\3rd\3.2_MUL_MENU-SSD1315 2 组件功能介绍 驱动OLED显示多级菜单,所有的OLED都可以使用这个组件,都可以使用本驱动。驱动使用模拟spi实现。 menu相关代码可以移植到其它任意尺寸屏幕上,修改menu.c相关显示尺寸即可。 3 代码讲解 1 menu_command_callback 功能:该函数用于,菜单指令回调函数。 参数: 参数 释义 command 起点坐标 ... 可变参数 返回值:无 示例: C menu_command_callback(GET_EVENT_ENTER) 2 menu_run_menu 功能:该函数用于,菜单运行函数。 参数: 参数 释义 hMENU 菜单句柄 返回值:无 示例: C menu_run_main_menu(); 3 menu_handle_init 功能:该函数用于,菜单初始化。 参数: 参数 释义 hMENU 菜单句柄 返回值:无 示例: C menu_handle_init(hMENU); // 初始化 4 menu_event_and_action 功能:该函数用于,处理相应按键事件。 参数: 参数 释义 hMENU 菜单句柄 返回值:无 示例: C menu_event_and_action(hMENU); // 检查事件及作相应操作 5 menu_updata_idx 功能:该函数用于,更新菜单选中下标。 参数: 参数 释义 hMENU 菜单句柄 返回值:无 示例: C menu_updata_idx(hMENU); 6 menu_show_option_list 功能:该函数用于,菜单显示列表。 参数: 参数 释义 hMENU 菜单句柄 返回值:无 示例: C menu_show_option_list(hMENU); /* 显示选项列表 */ 7 menu_show_option 功能:该函数用于,根据数据类型显示。 参数: 参数 释义 x,y 起始坐标 hMENU 菜单句柄 返回值:无 示例: C /* 显示选项, 并记录长度 */ Show_i + i]); 8 menu_show_cursor 功能:该函数用于,显示光标。 参数: 参数 释义 hMENU 菜单句柄 返回值:无 示例: C menu_show_cursor(hMENU); /* 显示光标 */ 9 menu_show_border 功能:该函数用于,显示边框。 参数: 参数 释义 hMENU 菜单句柄 返回值:无 示例: C menu_show_border(hMENU); // 显示边框 10 menu_oled_set_cursor 功能:该函数用于,画OLED设置显示光标位置。 参数: 参数 释义 Page 指定光标所在的页,范围:0~7 X 指定光标所在的X轴坐标,范围:0~127 返回值:无 示例: C /*设置光标位置为每一页的第一列*/ menu_oled_set_cursor(j, 0); 11 menu_oled_write_data 功能:该函数用于,OLED写数据。 参数: 参数 释义 Data 要写入数据的起始地址 Count 要写入数据的数量 返回值:无 示例: C /*连续写入128个数据,将显存数组的数据写入到OLED硬件*/ menu_oled_write_data(menu_display_buf , 128); 12 menu_oled_clear 功能:该函数用于,将OLED显存数组全部清零。 参数:无 返回值:无 示例: C drv_ssd1315_refresh(); 13 menu_oled_clear_area 功能:该函数用于,将OLED显存数组部分清零。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 Width 指定区域的宽度,范围:0~128 Height 指定区域的高度,范围:0~64 返回值:无 示例: C /*将图像所在区域清空*/ menu_oled_clear_area(X, Y, Width, Height); 14 menu_oled_show_image 功能:该函数用于,OLED显示图像。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 Width 指定区域的宽度,范围:0~128 Height 指定区域的高度,范围:0~64 Image 指定要显示的图像 返回值:无 示例: C /*将ASCII字模库OLED_F6x8的指定数据以6*8的图像格式显示*/ menu_oled_show_image(X, Y, 6, 8, OLED_F6x8 ); 15 menu_oled_show_char 功能:该函数用于,屏幕旋转180度。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 Char 指定要显示的字符,范围:ASCII码可见字符 FontSize 指定字体大小 范围:OLED_8X16 宽8像素,高16像素 OLED_6X8 宽6像素,高8像素 返回值:无 示例: C menu_oled_show_char(X + len * FontSize, Y, String , FontSize); 16 menu_oled_show_chn 功能:该函数用于,反显函数。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 Char 指定要显示的字符,范围:ASCII码可见字符 FontSize 指定字体大小 范围:OLED_8X16 宽8像素,高16像素 OLED_6X8 宽6像素,高8像素 返回值:无 示例: C menu_oled_show_chn(X + len * FontSize, Y, SingleChinese, FontSize); 17 menu_oled_show_str 功能:该函数用于,OLED显示字符串。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 Hanzi 指定要显示的字符,范围:字库字符 FontSize 指定字体大小 范围:OLED_8X16 宽8像素,高16像素 OLED_6X8 宽6像素,高8像素 返回值:无 示例: C menu_oled_show_str(X, Y, String, FontSize);//OLED显示字符数组(字符串) 18 menu_oled_printf 功能:该函数用于,OLED使用printf函数打印格式化字符串。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 FontSize 指定字体大小 范围:OLED_8X16 宽8像素,高16像素 OLED_6X8 宽6像素,高8像素 format 指定要显示的格式化字符串,范围:ASCII码可见字符组成的字符串 ... 格式化字符串参数列表 返回值:无 示例: C /* 按需使用参数 */ menu_oled_printf(show_x, show_y, MENU_FONT_W, show_string); // 显示字符串 19 menu_oled_reverse_area 功能:该函数用于,将OLED显存数组部分取反。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 Width 指定区域的宽度,范围:0~128 Height 指定区域的高度,范围:0~64 返回值:无 示例: C menu_oled_show_chn(X + len * FontSize, Y, SingleChinese, FontSize); 20 menu_oled_draw_point 功能:该函数用于,OLED在指定位置画一个点。 参数: 参数 释义 X 指定区域左上角的横坐标,范围:0~127 Y 指定区域左上角的纵坐标,范围:0~63 返回值:无 示例: C menu_oled_show_chn(X + len * FontSize, Y, SingleChinese, FontSize); 21 menu_keyX_irq_hadler 功能:该函数用于,中断回调函数。 参数:无 返回值:无 示例: C config.isr = menu_key0_irq_hadler; 22 menu_keyX_hadler 功能:该函数用于,回调函数。 参数:无 返回值:无 示例: C Os_Create_HISR(&menu_key0_hisr, "Gpio_Hisr", menu_key0_hadler, 2); 23 menu_keyX_callback 功能:该函数用于,去抖定时器回调函数。 参数:无 返回值:无 示例: C OSATimerStart(menu_key0_timer_ref, 2, 0, menu_key0_callback, 0); 24 menu_key_init 功能:该函数用于,初始化菜单按键使用的gpio。 参数:无 返回值:无 示例: C //初始化按键 menu_key_init(); 返回值:无 4 Demo实战 4.1 创建一个Demo 复制3.1_SSD1315示例工程,到同一个文件夹下,修改文件名为3.2_MUL_MENU-SSD1315,如图: 4.2 修改makefile 增加文件组件所在目录头文件路径,和源文件路径,如图: 4.3 增加头文件 使用代码编辑器,将新建的工程文件加入代码编辑器中,打开main.c,修改main.c,加入am.h等头文件,如图: 4.4 修改代码 在Phase2Inits_exit 创建一个任务,如图: 4.5 宏定义介绍 sample_ssd1315_uart_printf 输出日志到DEBUG 串口,日志比较少,可以输出到这个串口,如果日志比较多,需要输出到usb口,以免不必要的问题出现 sample_ssd1315_catstudio_printf 输出日志到USB 串口,使用catstudio查看,catstudio查看日志需要更新对应版本mdb.txt文件,软件打开filtter过滤日志,只查看用户输出的日志 SAMPLE_SSD1315_STACK_SIZE 栈空间宏定义 4.6 全局变量介绍 sample_ssd1315_stack_ptr 任务栈空间,本例使用数组实现,用户在做项目时,可以预先估算下当先任务需要的大致栈空间,OS没有提供可以查看栈空间使用情况的API sample_ssd1315_task_ref 任务指针 4.7 函数介绍 Phase1Inits_enter 底层初始化,本例空 Phase1Inits_exit 底层初始化,本例空 Phase2Inits_enter 底层初始化,本例空 Phase2Inits_exit 创建主任务,初始化消息队列,定时器,任务等。 代码片段: C int ret = 0; GPIOConfiguration config = {0}; //创建定时�? OSATimerCreate(&sample_xl9535_int_detect_timer_ref); //创建中断处理任务 OSATaskCreate(&sample_ssd1315_task_ref, sample_ssd1315_stack_ptr, SAMPLE_SSD1315_STACK_SIZE, 100, "ssd1315_task", sample_ssd1315_task, NULL); menu_run_main_menu 显示主界面,用户任务中调用这个函数。 代码片段: C static MENU_OPTION_TYPE_T MENU_OptionList = { {"TOOLS1", NULL}, {"TOOLS2", NULL}, {"TOOLS3", NULL}, {"TOOLS4", NULL}, {"TOOLS5", NULL}, {".."}}; static MENU_HANDLE_TYPE_T MENU = {.OptionList = MENU_OptionList}; menu_run_menu(&MENU); menu_run_tools_menu 根据按键选择运行工具界面。 代码片段: C static MENU_OPTION_TYPE_T MENU_OptionList SDK_CUST_SKU : SDK_PS_MODE : SDK_CHIP_VER : SDK_OS_TYPE : Platform Convertion Tools v4.01 with PS option extension Convertion done! |INPUT |out\bin\cp_1606L.bin |MARK |NAME |EXEADDR .LOADADDR.LENGTH .CPZLADDR|COMPRESS STASTIC | |--------|--------|--------.--------.--------.--------|------------------------------| |This Is LteOnly 4M| 00003000 |This Is LteOnly 4M| 00001000 |This Is LteOnly 4M| 0000a000 |This Is LteOnly 4M| 0001e000 |This Is LteOnly 4M| 0001b000 |This Is LteOnly 4M| 0001b000 |This Is LteOnly 4M| 0001a000 |This Is LteOnly 4M| 0001a000 |This Is LteOnly 4M| 00011000 |This Is LteOnly 4M| 0001e000 |This Is LteOnly 4M| 00021000 |This Is LteOnly 4M| 00012000 |--------|--------|--------.--------.--------.--------|------------------------------| 0x0014a000| 1.289(MB)| |------------------------------------------------------------------------------------| cp_1606L.axf cp_1606L.bin cp_1606L.map gnumake: Leaving directory `F:/3.asr-b/cat.1-asr1606/1.software/BlOOM_OS_1606_OPENCPU_1191_A09_WIHT_NEWRF/amaziot_bloom_os_sdk/sample/3rd/3.2_MUL_MENU-SSD1315' "copy NEZHAC_CP_CNR_MIFI_TX.bin to ./ " 已复制 1 个文件。 4.9 生成固件 参考入门中开发工具,生成工具。 4.10 测试 测试步骤: 参考编译教程,和文档开头的编译指令,进行编译 按照编译教程选择对应的选项 烧录 4.11 固件 上电后,屏幕会显示主菜单,通过4个按键:选择,返回,上,下。可以选择不同的菜单; 点击下载 OLED 多级菜单 Demo固件 5 生态组件链接 OLED屏 注:本文部分内容来源于网络,如有侵权,请及时联系我们。 本文章源自奇迹物联开源的物联网应用知识库Cellular IoT Wiki,更多技术干货欢迎关注收藏Wiki: Cellular IoT Wiki 知识库(https://rckrv97mzx.feishu.cn/wiki/wikcnBvAC9WOkEYG5CLqGwm6PHf)
相关资源