超级版主
 
- 积分
- 5327
- 金钱
- 5327
- 注册时间
- 2019-5-8
- 在线时间
- 1369 小时
|
本帖最后由 正点原子运营 于 2026-1-16 09:33 编辑
第三十六章 TWAI实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章,我们将介绍如何使用ESP32-P4自带的TWAI控制器来实现TWAI的收发功能,并将结果显示在LCD屏幕上。
本章分为如下几个小节:
36.1 TWAI总线协议介绍
36.2 硬件设计
36.3 程序设计
36.4 下载验证
36.1 TWAI总线协议介绍
有人说乐鑫的芯片没有CAN总线控制器,只不过它被叫做双线汽车接口,缩写为TWAI,详称为“two_wire automotive interface”。由于ESP32的CAN控制器在功能上相比STM32稍微逊色一点,同时资料也少一点,所以不被大家所熟知。
36.1.1 TWAI简介
双线汽车接口(TWAI)是一种适用于汽车和工业应用的实时串行通信协议,兼容ISO11898-1经典帧(CAN2.0),因此可以支持标准帧格式(11位ID)和扩展帧格式(29位ID)。但是不兼容ISO11898-1 FD格式帧,并会将这些帧解析为错误。
ESP32-P4包含3个TWAI控制器,分别是TWAI0、TWAI1和TWAI2。 ESP32-P4的TWAI控制器具有以下特性:
① 兼容ISO 11898-1协议(CAN规范2.0)
② 支持标准帧格式(11位ID)和扩展帧格式(29位ID)
③ 比特率从1Kbit/s到1Mbit/s
④ 多种操作模式:工作模式、只听模式和自检模式(传输无需确认)
⑤ 64字节接收FIFO
⑥ 特殊传输:单次传输(错误时不会自动重传)、自发自收(同时发送和接收消息)
⑦ 数据接收过滤器(支持单过滤器和双过滤器模式)
⑧ 错误检测与处理:错误计数、可配置的错误中断阈值、错误代码记录和仲裁丢失记录
双线汽车接口通过GPIO交换矩阵可配置使用任意GPIO管脚,非常灵活。
TWAI通信拓扑图如下图所示:
图36.1.1 TWAI通信拓扑结构图
从上图可知,TWAI总线呈现的是一个闭环结构,总线是由两根线TWAI_High和TWAI_Low组成,且在总线两端各串联了120Ω的电阻(用于阻抗匹配,减少回波反射),同时总线上可以挂载多个节点。每个节点都有TWAI收发器以及TWAI控制器,TWAI控制器通常是MCU的外设,集成在芯片内部,而TWAI收发器则是需要外加芯片转换电路。
TWAI类似RS485也是通过差分信号传输数据。根据TWAI总线上两根线的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。这是属于物理层特征,ISO11898物理层特性如图36.1.1.2所示:
图36.1.1.2 ISO11898物理层特性
从该特性可以看出,显性电平对应逻辑0,CAN_H和CAN_L之差为2 V左右。而隐性电平对应逻辑1,CAN_H和CAN_L之差为0V。在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。
36.1.2 TWAI总线协议特点
TWAI是一种多主机、多播、异步、串行通信协议,该协议还支持错误检测和通报,并具有内置报文优先级。
多主机:总线上的任何节点都可以发起报文传输。
多播:节点传输报文时,总线上的所有节点都会接收该报文(即广播),确保所有节点数据一致。但通过接收过滤,某些节点可以选择性地接收报文(多播)。
异步:总线不包含时钟信号。总线上的所有节点以相同的位速率运行,并使用在总线上传输位的边沿进行同步。
错误检测和通报:每个节点不断监听总线。节点检测到错误时,通过传输错误帧通报检测到的错误。其他节点会接收错误帧,并传输自己的错误帧作为回应,这样一来检测的错误即可传播到总线上的所有节点。
故障限制:若一组错误计数依据规定增加/减少时,各节点将维护该组错误计数。当错误计数超过一定阈值时,对应节点将自动关闭以退出网络。
报文优先级:每个报文包含唯一的ID字段。如果两个或多个节点尝试同时传输,ID小的节点将获得总线的控制权,而其他节点将自动转为接收器,确保无论何时最多只有一个发射器。
发送器与接收器:无论何时,任何TWAI节点都可作为发送器和接收器。产生报文的节点为发送器。所有非发送器的节点都将作为接收器。
36.1.3 TWAI总线协议
TWAI节点使用报文发送数据,并在检测到总线上存在错误时向其他节点发送错误信号。报文分为不同的帧类型,某些帧类型将具有不同的帧格式。
TWAI总线协议是通过以下5种类型的帧进行的:
数据帧
遥控帧
错误帧
过载帧
间隔帧
另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11个位的标识符(ID),扩展格式有29个位的ID。各种帧的用途如表36.1.3.1所示:
表36.1.3.1 TWAI协议各种帧及其用途
由于篇幅所限,我们这里仅对数据帧进行详细介绍,数据帧一般由7个段构成,即:
帧起始。表示数据帧开始的段。
仲裁段。表示该帧优先级的段。
控制段。表示数据的字节数及保留位的段。
数据段。数据的内容,一帧可发送0~8个字节的数据。
CRC段。检查帧的传输错误的段。
ACK段。表示确认正常接收的段。
帧结束。表示数据帧结束的段。
数据帧的构成如图36.1.2.1所示:
图36.1.3.1数据帧的构成
图中D表示显性电平,R表示隐形电平(下同)。
帧起始,这个比较简单,标准帧和扩展帧都是由1个位的显性电平表示帧起始。
仲裁段,表示数据优先级的段,标准帧和扩展帧格式在本段有所区别,如图36.1.3.2所示:
图36.1.3.2 数据帧仲裁段构成
标准格式的ID有11个位。禁止高7位都为隐性(禁止设定:ID=1111111XXXX)。扩展格式的ID有29个位。基本ID从ID28到ID18,扩展ID由ID17到ID0表示。基本ID和标准格式的ID相同。禁止高7位都为隐性(禁止设定:基本ID=1111111XXXX)。
其中RTR位用于标识是否是远程帧(0,数据帧;1,远程帧),IDE位为标识符选择位(0,使用标准标识符;1,使用扩展标识符),SRR位为代替远程请求位,为隐性位,它代替了标准帧中的RTR位。
控制段,由6个位构成,表示数据段的字节数。标准帧和扩展帧的控制段稍有不同,如图36.1.3.3所示:
图36.1.3.3 数据帧控制段构成
上图中,r0和r1为保留位,必须全部以显性电平发送,但是接收端可以接收显性、隐性及任意组合的电平。DLC段为数据长度表示段,高位在前,DLC段有效值为0~8,但是接收方接收到9~15的时候并不认为是错误。
数据段,该段可包含0~8个字节的数据。从最高位(MSB)开始输出,标准帧和扩展帧在这个段的定义都是一样的。如图36.1.3.4所示:
图36.1.3.4 数据帧数据段构成
CRC段,该段用于检查帧传输错误。由15个位的CRC顺序和1个位的CRC界定符(用于分隔的位)组成,标准帧和扩展帧在这个段的格式也是相同的。如图36.1.3.5所示:
图36.1.3.5 数据帧CRC段构成
此段CRC的值计算范围包括:帧起始、仲裁段、控制段、数据段。接收方以同样的算法计算CRC值并进行比较,不一致时会通报错误。
ACK段,此段用来确认是否正常接收。由ACK槽(ACKSlot)和ACK界定符2个位组成。标准帧和扩展帧在这个段的格式也是相同的。如图36.1.3.6所示:
图36.1.3.6 数据帧CRC段构成
发送单元的ACK,发送2个位的隐性位,而接收到正确消息的单元在ACK槽(ACKSlot)发送显性位,通知发送单元正常接收结束,这个过程叫发送ACK/返回ACK。发送ACK的是在既不处于总线关闭态也不处于休眠态的所有接收单元中,接收到正常消息的单元(发送单元不发送ACK)。所谓正常消息是指不含填充错误、格式错误、CRC错误的消息。
帧结束,这个段也比较简单,标准帧和扩展帧在这个段格式一样,由7个位的隐性位组成。至此,数据帧的7个段就介绍完了,其他帧的介绍,请大家参考光盘的《CAN入门书.pdf》相关章节。
接下来,我们再来看看TWAI的位时序。由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为3段。
同步段(SS)
相位缓冲段1(PBS1)
相位缓冲段2(PBS2)
这些段又由可称为Time Quanta(以下称为Tq)的最小时间单位构成。
1位分为3个段,每个段又由若干个Tq构成,这称为位时序。
1位由多少个Tq构成、每个段又由多少个Tq构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。各段的作用和Tq数如表36.1.3.2所示:
表36.1.3.2 一个位各段及其作用
1个位的构成如图36.1.3.7所示:
图36.1.2.7 一个位的构成
上图的采样点,是指读取总线电平,并将读到的电平作为位值的点。位置在PBS1结束处。根据这个位时序,我们就可以计算TWAI通信的波特率了。具体计算方法,我们等下再介绍。
由于时钟偏移和抖动,同一总线上节点的位时序可能会脱离相位段。因此,位边沿可能会偏移到同步段的前后。针对位边沿偏移的问题,TWAI提供了多种同步方式。设位边沿偏移的Tq数量为相位错误“e”该值与SS相关。
主动相位错误(e>0):位边沿位于同步段之后采样点之前(边沿向后偏移)。
被动相位错误(e<0):位边沿位于前个位的采样点之后同步段之前(边沿向前偏移)。
为了解决相位错误,可进行两种同步方式是硬同步和再同步。硬同步和再同步遵守以下规则:① 单个位时序中仅可发生一次同步;② 同步仅可发生在隐形位到显性位的边沿上。
硬同步:总线空闲期间,硬同步发生在隐性位到显性位的变化边沿上(如总线空闲后的第一个SOF位)。此时,所有节点都将重启其内部位时序,从而使该变化边沿位于重启位时序的同步段内。
再同步:非总线空闲期间,再同步发生在隐性位到显性位的变化边沿上。如果边沿上有主动相位错误(e>0),则PBS1长度将增加。如果边沿上有被动相位错误(e<0),PBS2长度将减小。
PBS1/PBS2具体增加和减小的时间定额取决于相位错误的绝对值,同时也受可配置的同步跳宽(SJW)数值限制。
当相位错误的绝对值小于等于SJW数值时,PBS1/PBS2将增加/减小e个时间定额。该过程与硬同步具有相同效果。
当相位错误的绝对值大于SJW数值时,PBS1/PBS2将增加/减小与SJW相同数值的时间定额。这意味着,在完全解决相位错误之前,可能需要多个同步位。
前面提到的TWAI协议具有仲裁功能,下面我们来看看是如何实现的。
在总线空闲态,最先开始发送消息的单元获得发送权。当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。实现过程,如图36.1.3.8所示:
图36.1.3.8 TWAI总线仲裁过程
上图中,单元1和单元2同时开始向总线发送数据,开始部分他们的数据格式是一样的,故无法区分优先级,直到T时刻,单元1输出隐性电平,而单元2输出显性电平,此时单元1仲裁失利,立刻转入接收状态工作,不再与单元2竞争,而单元2则顺利获得总线使用权,继续发送自己的数据。这就实现了仲裁,让连续发送显性电平多的单元获得总线使用权。
简单来说,有以下规律:① ID值最小的帧将总是获得仲裁;② 如果ID和格式相同,由于数据帧的RTR位为显性位,数据帧将优先于远程帧;③ 如果ID的前11位相同,由于扩展帧的SRR位是隐性,因而标准格式帧将总优先于扩展格式帧。
36.1.4 TWAI控制器结构
TWAI控制器的结构框图如下图所示。
图36.1.4.1 ESP32-P4的TWAI结构框图
TWAI控制器主要由寄存器模块(Registers)、位流处理器(Bit Stream Processing)、错误管理逻辑(Error Management Logic)、位时序逻辑(Bit Timing Logic)、接收过滤器(Acceptance Filter)和接收FIFO (Receive FIFO)。
寄存器模块
TWAI控制器包括比较多的寄存器,比如:
① 配置寄存器,用于存储TWAI控制器的各配置项,如位速率、操作模式、接收过滤器等。
② 指令寄存器,CPU通过该寄存器驱动TWAI控制器执行任务,如发送报文或清除接收缓冲器。
③ 中断&状态寄存器,用于显示TWAI控制器中发生的事件。
④ 错误管理寄存器,包含错误计数和捕捉寄存器。错误计数寄存器存放的是发送错误或接收错误的累积次数,而捕捉寄存器负责记录相关信息,比如何处检测出总线错误,丢失仲裁等。
⑤ 发送缓冲寄存器,大小为13字节,用于存储TWAI的待发送报文。
⑥ 接收缓冲寄存器,大小为13字节,用于存储单个报文。接收缓冲器是进入接收FIFO的窗口,接收FIFO中的第一个报文将被映射到接收缓冲器中。
位流处理器
位流处理器BSP模块负责对发送缓冲器的数据进行帧处理(如位填充和附加CRC域)并为位时序逻辑模块BTL生成位流。同时,BSP模块还负责处理从BTL模块中接收的位流(如去填充和验证CRC),并将处理报文置于接收FIFO。BSP还负责检测TWAI总线上的错误并将此类错误报告给错误管理逻辑EML。
错误管理逻辑
错误管理逻辑EML模块负责更新TEC和REC数值,记录错误信息(如错误类型和错误位置),更新控制器的错误状态,确保BSP模块发送正确的错误标志。此外,该模块还负责记录TWAI控制器丢失仲裁时的bit位置。
位时序逻辑
位时序逻辑BTL模块负责以预先配置的位速率发送和接收报文。BTL模块还负责同步位时序,确保数据传输的稳定性。位速率由多个可编程的段组成,且用户可设置每个段的Tq长度,来调整传播延迟、控制器处理时间等因素。
接收过滤器
接收过滤器是一个可编程的报文过滤单元,允许TWAI控制器根据报文的标识符域接收或拒绝该报文。通过接收滤波器的报文才能被存储到接收FIFO中。用户可配置接收滤波器的模式:单滤波器、双滤波器。
接收FIFO
接收FIFO是大小为64字节的缓冲器(位于TWAI控制器内部),负责存储通过接收滤波器的接收报文。接收FIFO中存储的报文大小可以不同。当接收FIFO为满时(或剩余的空间不足以完全存储下一个接收报文),将触发溢出中断,后续的接收报文将丢失,直到接收FIFO中清除出足够的储存空间。接收FIFO中的第一条报文将映射到13字节的接收缓冲器中,直到该报文被清除(通过释放接收缓冲器指令)。清除后,接收缓冲器将继续映射接收FIFO中的下一条报文,接收FIFO中上一条已清除报文的空间将被释放。
36.1.5 TWAI控制器接收过滤器
ESP32-P4 TWAI控制器内置硬件接收过滤器,可以过滤特定ID的报文。只有通过过滤的报文才能存储到接收FIFO中。接收过滤器的使用可以一定程度减轻TWAI控制器的运行负荷(如克减少使用接收FIFO和发生接收中断的次数),因为TWAI控制器将只需要操作一小部分过滤后的报文。
只有当TWAI控制器处于复位模式时,才可以访问接收过滤器的配置寄存器,因为这些配置寄存器和发送/接收缓冲寄存器的地址空间相同。
接收过滤器的配置寄存器由32bit的Code值(接收码)和32bit的Mask值(接收掩码)组成。Code值将指定一种位排列模式,每条过滤报文中的位都必须匹配该模式,才能使该报文通过过滤。Mask值可屏蔽Code值中的某些位(将屏蔽位设置为“不相关”的位)。为了使报文通过过滤,每条过滤报文ID都必须匹配Code值所设模式或被Mask值屏蔽。过滤过程如下所示。
图36.1.5.1 接收过滤器过滤过程
TWAI控制器的接收过滤器允许32bit的Code值和Mask值定义单个过滤器(单过滤器模式)或两个过滤器(双过滤器模式)。接收过滤器如何解析32bit的Code值和Mask值,取决于过滤模式以及接收报文的格式。
单过滤模式使用接收码和接收掩码定义了一个过滤器,支持过滤标准帧的前两个数据字节,或扩展帧的29位ID的全部内容,如下图所示。
图36.1.5.2 单过滤模式
在单过滤器模式下解析32位接收代码和掩码的方式。黄色代表的是标准帧格式,而蓝色代表的是扩展帧格式。
双过滤器模式使用接收代码和掩码定义两个单独过滤器,支持接收更多ID,但不支持过滤扩展ID的全部29位,如下图所示。
图36.1.5.3 双过滤模式
在双过滤器模式下解析32位接收代码和掩码的方式。黄色代表的是标准帧格式,而蓝色代表的是扩展帧格式。
36.1.6 TWAI控制器模式
ESP32-P4 TWAI控制器有两种工作模式:复位模式和操作模式。
复位模式
要修改TWAI控制器的各种配置寄存器,需进入复位模式。进入复位模式时,TWAI控制器彻底与TWAI总线断开连接。复位模式下,TWAI控制器将无法发送任何报文(包括错误信号)。任何正在进行的报文传输将立即被终止。同样的,TWAI控制器在该模式下也将无法接收任何报文。
操作模式
进入操作模式后,TWAI控制器与总线相连,并且写保护各配置寄存器,以确保控制器的配置在运行期间保持一致。操作模式下,TWAI控制器可以发送和接收报文(包括错误信号),但具体取决于TWAI控制器配置于哪种运行子模式。TWAI控制器支持以下三种子模式:
正常模式:TWAI控制器可以发送和接收包含错误信号在内的报文(如错误帧和过载帧)。发送报文时需要来自另一个节点的应答。
自测模式(无应答模式):与正常模式类似,但在该模式下,TWAI控制器发送报文时,即使在CRC域之后没有接收到应答信号,也不会产生应答错误。通过在TWAI控制器自测时使用该模式。
只听模式:TWAI控制器可以接收报文,但在TWAI总线上保持完全被动。因此,TWAI控制器将无法发送任何报文、应答或错误信号。错误计数将保持冻结状态。该模式用于TWAI总线监控。
注意:退出复位模式后(如进入操作模式时),TWAI控制器需要等待11个连续隐性位出现,才能完全连接上TWAI总线,发送或接收报文。
36.1.7 TWAI波特率
TWAI总线上的各个节点只要约定好1个Tq的时间长度以及每一个数据位占据多少个Tq,就可以确认TWAI通信的波特率。
假如1Tq = 0.1us,而每个数据位由20个Tq组成,则传输一位数据需要时间T1bit = 2us,从而得到每秒钟可以传输的数据位个数:
1 x 106 / 2= 500k (bps) 这个每秒可传输的数据位个数即为通信的波特率。
在ESP32-P4的TWAI控制器中,Tq时钟是由XTAL时钟分配得到的,XTAL时钟为40MHz。可通过以下公式计算。
其中,tTq为时间定额的时钟周期,tCLK为XTAL时钟周期,而BRP为预分频值。
然后,得到一个数据位的时间即(PBS1+PBS2+SS)* tTq,最终可推导出如下公式:
Baud = 1 / ((PBS1 + PBS2 + SS) * 2 * tCLK * (BRP + 1))
36.2 硬件设计
36.2.1 例程功能
通过KEY1按键选择正常模式;通过KEY2按键选择不应答模式。通过KEY0控制数据发送,接着查询是否有数据接收到。假如接收到数据,就将接收到的数据显示在LCD屏上。如果 是无应答模式,我们不需要2个开发板。如果是正常模式,我们就需要两个ESP32-P4开发板,并将他们的TWAI接口连接起来,然后用一个开发板发送数据,另外一个开发板将接收到的数据显示在LCD屏上。LED0用来指示程序正在运行。
36.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)RGBLCD / MIPILCD(引脚太多,不罗列出来)
3)TWAI电路
CAN_TX - IO27(U1RXD)(跳线帽连接)
CAN_RX - IO26(U1TXD)(跳线帽连接)
36.2.3 原理图
CAN电路相关原理图,如下图所示。
图36.2.3.1 CAN电路原理图
从上图可以看出:开发板的TWAI电路通过P5的设置,连接到TJA1050/SIT1050T收发芯片,然后通过接线端子(CAN)同外部的CAN总线连接。图中可以看出,在ESP32-P4开发板上是带有120Ω的终端电阻的,如果我们的开发板不是作为CAN的终端的话,需要把这个电阻去掉,以免影响通信。另外,需要注意:TWAI、485公用IO26和IO27,他们不能同时使用。
这里还需要注意,我们要设置好开发板上的P5排针的连接,通过跳线帽将IO26和IO27分别连接到CAN_RX和CAN_TX上面,如图36.2.3.2所示。
图36.2.3.2 TWAI实验需要跳线连接的位置
最后,我们用2根导线将两个开发板CAN端子的CAN_L和CAN_L,CAN_H和CAN_H连接起来。这里注意不要接反了(CAN_L接CAN_H),接反了会导致通信异常!!!
36.3 程序设计
36.3.1 TWAI的IDF驱动
使用TWAI相关功能函数,必须先导入以下头文件:
接下来,作者将介绍一些常用的函数,这些函数的描述及其作用如下:
1,创建TWAI函数twai_driver_install
该函数用于创建TWAI,其函数原型如下:
- <font size="3">esp_err_t twai_driver_install(const twai_general_config_t *g_config, </font>
- <font size="3">const twai_timing_config_t *t_config, </font>
- <font size="3">const twai_filter_config_t *f_config);</font>
复制代码 函数形参:
表36.3.1.1 twai_driver_install函数形参描述
函数返回值:
ESP_OK表示创建成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_NO_MEM表示内存不足。
ESP_ERR_INVALID_STATE表示驱动已经被安装。
g_config为指向TWAI通用配置结构体指针。接下来,介绍twai_general_config_t结构体中各个成员,如下代码所示:
- typedef struct {
- int controller_id; /* TWAI控制器ID */
- twai_mode_t mode; /* TWAI控制器模式 */
- gpio_num_t tx_io; /* 发送引脚 */
- gpio_num_t rx_io; /* 接收引脚 */
- gpio_num_t clkout_io; /* 时钟输出引脚 */
- gpio_num_t bus_off_io; /* 离线通知引脚 */
- uint32_t tx_queue_len; /* TX队列可容纳的消息数 */
- uint32_t rx_queue_len; /* RX队列可容纳的消息数 */
- uint32_t alerts_enabled; /* 要启动的警报位字段 */
- uint32_t clkout_divider; /* CLKOUT分频器 */
- int intr_flags; /* 中断优先级 */
- struct {
- uint32_t sleep_allow_pd; /* 允许断电 */
- } general_flags; /* 通用标志 */
- } twai_general_config_t;
复制代码 twai_general_config_t结构体用于设置TWAI控制器的基本参数,以下对各成员做简单介绍。
1)controller_id:
设置TWAI控制器的ID。ESP32-P4有三个TWAI,当选择0时,才使用这个函数。非零时,需要选择twai_driver_install_v2函数进行创建TWAI。
2)mode:
设置TWAI操作模式。三个选项分别是TWAI_MODE_NORMAL、TWAI_MODE_NO_ACK和TWAI_MODE_LISTEN_ONLY,对应着36.1.5小节描述的三个操作模式。
3)tx_io
TWAI发送引脚,用于与外部收发器通信。
4)rx_io
TWAI接收引脚,用于与外部收发器通信。
5)clkout_io
CLKOUT信号线属于TWAI控制器的信号线,是可选的,会输出控制器源时钟的分配时钟。
6)buf_off_io
BUS-OFF信号线属于TWAI控制器的信号线,是可选的,在TWAI控制器进入离线状态时为低逻辑电平(0V)。否则为高逻辑电平(3.3V)。
7)tx_queue_len
用于设置TX队列可容纳的消息数。若设置为0即禁用TX队列。
8)rx_queue_len
用于设置RX队列可容纳的消息数。
9)alerts_enabled
用于启用的警报位字段,也就是对哪些标志进行使能。
10)clkout_divider
用于设置CLKOUT分频器。可以是1或2到14之间的任何偶数。如果不使用可设置为0。
11)intr_flags
用于设置中断优先级。
12)sleep_allow_pd
用于设置允许断电。当设置此标志时,驱动程序将在进入低功耗模式前,对TWAI寄存器的内容进行备份。驱动程序退出低功耗模式后,对TWAI寄存器内容进行恢复。
TWAI的IDF驱动提供了通用配置初始化宏,代码如下:
- TWAI_GENERAL_CONFIG_DEFAULT_V2(controller_num, tx_io_num, rx_io_num, op_mode)
- TWAI_GENERAL_CONFIG_DEFAULT(tx_io_num, rx_io_num, op_mode)
复制代码 第一个宏函数是配置TWAI1或TWAI2,而第二个宏函数是配置TWAI0的。使用宏函数就可以不对其结构体成员赋值,比较方便。但为了方便大家了解其成员,例程中还是采用了对各个成员赋值的方式。
t_config为指向TWAI时序配置结构体指针。介绍twai_timing_config_t结构体中各个成员,如下代码所示:
- typedef struct {
- twai_clock_source_t clk_src; /* TWAI控制器时钟源 */
- uint32_t quanta_resolution_hz; /* 时间定额的分辨率 */
- uint32_t brp; /* 波特率分频器 */
- uint8_t tseg_1; /* 时序段1 */
- uint8_t tseg_2; /* 时序段2 */
- uint8_t sjw; /* 同步跳变宽度 */
- bool triple_sampling; /* 三重采样 */
- } twai_timing_config_t;
复制代码 twai_timing_config_t结构体用于设置位速率,以下对各成员做简单介绍。
1)clk_src:
用来设置时钟源。设置为0或使用TWAI_CLK_SRC_DEFAULT,使用默认时钟源。
2)quanta_resolution_hz:
用来设置时间定额Tq的分辨率,单位为Hz。这里会跟brp的值由该字段反映。
3)brp:
用来设置TWAI控制器的源时钟分频,确定每个时间定额Tq的周期。在ESP32-P4上,brp可以是从2到32768的任何偶数。也可将quanta_resulotion_hz设置为非零值,决定时间定额的分辨率,由驱动程序计算出底层brp值。该方法适用于需要设置不同的时钟源,但希望位速率保持不变的情况。
4)tseg_1:
用来设置SEG1即时序段1的时间定额数量。
5)tseg_2:
用来设置SEG2即时序段2的时间定额数量。
6)sjw:
用来设置单位时间可以为了同步而延长或缩短的最大事件定额数。
7)triple_sampling
用来设置三重采样,有利于过滤低/中速总线的尖峰信号。启用三重采样会导致每个位采样3个时间定额,而不是1个,额外的采样点位置位于时序段1尾部。
注意:brp、tseg1、tseg2和sjw的不同组合可以实现相同位速率。用户应该考虑传播延迟、节点信息处理时间和相位误差等因素,根据总线的物理特性进行调整。
常见的位速率时序可以使用初始化宏。TWAI的IDF驱动提供了以下的初始化宏。
- TWAI_TIMING_CONFIG_25KBITS() /* 波特率25K */
- TWAI_TIMING_CONFIG_50KBITS() /* 波特率50K */
- TWAI_TIMING_CONFIG_100KBITS() /* 波特率100K */
- TWAI_TIMING_CONFIG_125KBITS() /* 波特率125K */
- TWAI_TIMING_CONFIG_250KBITS() /* 波特率250K */
- TWAI_TIMING_CONFIG_500KBITS() /* 波特率500K */
- TWAI_TIMING_CONFIG_800KBITS() /* 波特率800K */
- TWAI_TIMING_CONFIG_1MBITS() /* 波特率1M */
复制代码 f_config为指向TWAI过滤器的配置结构体指针。介绍twai_filter_config_t结构体中各个成员,如下代码所示:
- typedef struct {
- uint32_t acceptance_code; /* 接收码 */
- uint32_t acceptance_mask; /* 接收掩码 */
- bool single_filter; /* 单过滤器模式 */
- } twai_filter_config_t;
复制代码 twai_filter_config_t结构体用于设置接收过滤器,以下对各成员做简单介绍。
1)acceptance_code:
用来设置接收码。接收码指定报文的ID、RTR和数据字节必须匹配的位序列,使报文可以由TWAI控制器接收。
2)acceptance_mask:
用来设置接收掩码。接收掩码是一个位序列,指定接收码中可以忽略的位,从而实现用单个接收码接收不同ID的报文。
3)single_filter:
用来设置使用单过滤器模式还是双过滤器模式。单过滤器模式使用接收代码和掩码定义一个过滤器,支持筛选标准帧的前两个数据字节,或扩展帧的29位ID的全部内容。双过滤器模式使用接收代码和掩码定义两个单独的过滤器,支持接收更多ID,但不支持筛选扩展ID的全部29位。
TWAI的IDF驱动提供了过滤器的初始化宏,可用TWAI_FILTER_CONFIG_ACCEPT_ALL去替代对其成员赋值,去接收所有ID。
2,启动TWAI函数twai_start
该函数用于启动TWAI,允许参与TWAI总线活动,比如发送/接收消息,其函数原型如下:
- esp_err_t twai_start(void);
复制代码 无函数形参
函数返回值:
ESP_OK表示TWAI驱动在运行。
ESP_ERR_INVALID_STATE表示驱动不在停止状态或没有被驱动安装。
与twai_start函数对应的便是twai_stop,用于停止TWAI。
3,发送报文函数twai_transmit
该函数用于向TWAI总线上发送报文信息,其函数原型如下:
- esp_err_t twai_transmit(const twai_message_t *message,
- TickType_t ticks_to_wait);
复制代码 函数形参:
表36.3.1.2 twai_transmit函数形参描述
函数返回值:
ESP_OK表示发送成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_TIMEOUT表示等待TX队列空间超时。
ESP_FAIL表示TX队列被禁用,或当前正在发送另外一条报文。
ESP_ERR_INVALID_STATE表示TWAI驱动不在运行状态,或没有安装。
ESP_ERR_NOT_SUPPORT表示只听模式不支持传输。
message为指向报文配置结构体指针。介绍twai_message_t结构体中各成员,如下代码所示:
- typedef struct {
- union {
- struct {
- uint32_t extd: 1; /* 标准帧 / 扩展帧格式(29位ID) */
- uint32_t rtr: 1; /* 数据帧或远程帧 */
- uint32_t ss: 1; /* 报文是否单次发送 */
- uint32_t self: 1; /* 报文是否为自收发 */
- uint32_t dlc_non_comp: 1; /* 数据长度代码,不能大于8 */
- uint32_t reserved: 27; /* 保留 */
- };
- uint32_t flags; /* 标志 */
- };
- uint32_t identifier; /* 报文ID */
- uint8_t data_length_code; /* 报文携带数据的长度 */
- uint8_t data[TWAI_FRAME_MAX_DLC]; /* 报文携带的数据 */
- } twai_message_t;
复制代码 twai_message_t结构体用于设置接收过滤器,以下对各成员做简单介绍。
1)extd:
用来设置报文是标准帧还是扩展帧格式。若要标准帧,这里要设置为0,若要扩展帧,这里要设置为1。
2)rtr:
用来设置报文是数据帧还是远程帧。若是数据帧,这里要设置为0,若是远程帧,这里要设置为1。
3)ss:
用来设置报文是否单次发送。若是单次发送,这里要设置为1,若是要错误重发,这里要设置为0。
4)self:
用来设置报文是否自收发。若要接收自发的报文,这里设置为1即可。
5)dlc_non_comp:
用来设置报文的数据长度代码,ISO11898标准下不能超过8。
6)identifier:
用来设置报文的ID。若extd为0即标准帧,这里便是11位ID。若extd为1即扩展帧,这里便是29位ID。
7)data_length_code:
用来设置报文携带数据的长度。
8)data[TWAI_FRAME_MAX_DLC]:
用来存放报文携带的数据。
注意:这个函数将待发送的报文在TX队列上排队等待传输。如果没有其他报文排队,报文可立马传输。如果TX队列满了,该函数将会阻塞,直到有空间可用或超时。该函数在只听模式下不可用。
4,接收报文函数twai_receive
该函数用于从RX队列中接收报文,其函数原型如下:
- esp_err_t twai_receive(twai_message_t *message, TickType_t ticks_to_wait);
复制代码 函数形参:
表36.3.1.3 twai_receive函数形参描述
函数返回值:
ESP_OK表示接收成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_TIMEOUT表示等待超时。
ESP_ERR_INVALID_STATE表示TWAI驱动没有安装。
36.3.2 程序流程图
图36.3.2.1 TWAI实验程序流程图
36.3.3 程序解析
在27_twai例程中,作者在27_twai \components\BSP路径下新增了1个文件夹TWAI,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. TWAI驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。TWAI驱动源码包括两个文件:esp_twai.c和esp_twai.h。
下面先解析esp_twai.h的程序。对TWAI相关引脚以及报文的ID做了相关宏定义。
- <font size="3">#define TWAI_TX_GPIO_PIN GPIO_NUM_27</font>
- <font size="3">#define TWAI_RX_GPIO_PIN GPIO_NUM_26</font>
- <font size="3">#define MSG_ID 0x12</font>
复制代码 TWAI的发送引脚用到的是IO26,接收引脚用到的是IO27。
下面我们再解析twai.c的程序,首先先来看一下初始化函数twai_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief TWAI初始化</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] mode: TWAI控制器操作模式</font>
- <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url] TWAI_MODE_NORMAL:正常模式</font>
- <font size="3"> * @note TWAI_MODE_NO_ACK:无应答模式</font>
- <font size="3"> * @retval ESP_OK:表示初始化成功</font>
- <font size="3"> */</font>
- <font size="3">esp_err_t twai_init(twai_mode_t mode)</font>
- <font size="3">{</font>
- <font size="3"> if (tawi_state == 0xFF)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(twai_stop());</font>
- <font size="3"> ESP_ERROR_CHECK(twai_driver_uninstall());</font>
- <font size="3"> tawi_state = 0x00;</font>
- <font size="3"> }</font>
- <font size="3"> tawi_mode = mode;</font>
- <font size="3"> /* TWAI接口基本配置 */</font>
- <font size="3"> twai_general_config_t twai_config = {</font>
- <font size="3"> .controller_id = 0, /* TWAI控制器编号 */</font>
- <font size="3"> .mode = mode, /* TWAI控制器操作模式 */</font>
- <font size="3"> .tx_io = TWAI_TX_GPIO_PIN, /* 发送引脚 */</font>
- <font size="3"> .rx_io = TWAI_RX_GPIO_PIN, /* 接收引脚 */</font>
- <font size="3"> .clkout_io = TWAI_IO_UNUSED, /* 时钟输出引脚 */</font>
- <font size="3"> .bus_off_io = TWAI_IO_UNUSED, /* 总线离线引脚 */</font>
- <font size="3"> .tx_queue_len = 5, /* 发送队列长度 */</font>
- <font size="3"> .rx_queue_len = 5, /* 接收队列长度 */</font>
- <font size="3"> .alerts_enabled = TWAI_ALERT_NONE, /* 警告标志 */</font>
- <font size="3"> .clkout_divider = 0, /* 时钟分频,1to14,0不用 */</font>
- <font size="3"> .intr_flags = ESP_INTR_FLAG_LEVEL1, /* 中断优先级 */</font>
- <font size="3"> };</font>
- <font size="3"> /* TWAI接口位速率配置(官方提供1K到1Mbps常用配置宏) */</font>
- <font size="3"> /* Note 波特率计算公式: baud = Ftwai / (tseg_1 + tseg_2 + tss) / brp */</font>
- <font size="3"> twai_timing_config_t timing_config = { </font>
- <font size="3"> .clk_src = TWAI_CLK_SRC_DEFAULT, /* 时钟源 */ </font>
- <font size="3"> .quanta_resolution_hz = 10000000, /* 时间单元的分辨率 */</font>
- <font size="3"> .brp = 0, /* 时钟分频器 */</font>
- <font size="3"> .tseg_1 = 15, /* 时间段1 */</font>
- <font size="3"> .tseg_2 = 4, /* 时间段2 */</font>
- <font size="3"> .sjw = 3, /* 再次同步跳跃宽度*/</font>
- <font size="3"> .triple_sampling = false, /* 三重采样 */</font>
- <font size="3"> };</font>
- <font size="3"> /* 过滤器配置 */</font>
- <font size="3"> twai_filter_config_t filter_config = {</font>
- <font size="3"> .acceptance_code = 0, /* 接收码 */</font>
- <font size="3"> .acceptance_mask = 0xFFFFFFFF, /* 接收掩码 0xFFFFFFFF表示全部接收 */</font>
- <font size="3"> .single_filter = true, /* true:单过滤器模式;false:双过滤器模式 */</font>
- <font size="3"> };</font>
- <font size="3">ESP_ERROR_CHECK(twai_driver_install(&twai_config, &timing_config, </font>
- <font size="3">&filter_config)); /* 安装TWAI驱动 */</font>
- <font size="3"> ESP_ERROR_CHECK(twai_start()); /* 启动TWAI驱动 */</font>
- <font size="3"> tawi_state = 0xFF;</font>
- <font size="3"> return ESP_OK;</font>
- <font size="3">}</font>
复制代码 在TWAI初始化函数中,首先对twai_config结构体变量成员进行赋值,选择使用TWAI0,TWAI的操作模式由形参mode决定,TWAI的发送引脚选择使用的是IO27,接收引脚选择使用的是IO26,不使用CLKOUT和BUS_OFF引脚,发送和接收队列长度都设置为5。然后对timing_config结构体变量成员进行赋值,通过对clk_src、quanta_resolution_hz、tseg_1和tseg_2成员搭配设置,最终得到500k bps波特率。波特率达到500k bps时,就不采用三重采样。接着对filter_config结构体变量成员进行赋值,这里的配置直接接收所有ID的报文,并没有过滤特定ID的报文。最后调用twai_driver_install函数创建TWAI总线,调用twai_start函数启动TWAI总线,往后便可以通过twai_transmit函数发送报文,twai_receive函数接收报文。
接下来,看一下TWAI发送报文的函数twai_send_data,代码如下。
- <font size="3">/**</font>
- <font size="3"> * @brief TWAI发送一帧数据</font>
- <font size="3"> * @note 发送格式固定为:标准ID,数据帧</font>
- <font size="3"> * @param id:标准ID (11位)</font>
- <font size="3"> * @param msg:数据指针,最大8个字节</font>
- <font size="3"> * @param len:数据长度(最大8)</font>
- <font size="3"> * @retval ESP_OK成功; ESP_FAIL失败</font>
- <font size="3"> */</font>
- <font size="3">esp_err_t twai_send_data(uint32_t id, uint8_t msg[], uint8_t len)</font>
- <font size="3">{</font>
- <font size="3"> twai_message_t tx_msg = { /* 设置报文类型及格式 */</font>
- <font size="3"> .extd = 0, /* 0标准帧; 1扩展帧 */</font>
- <font size="3"> .rtr = 0, /* 0数据帧; 1远程帧 */</font>
- <font size="3"> .ss = 1, /* 0错误重发; 1单次发送(仲裁或丢失时消息不会被重发) */</font>
- <font size="3"> .self = 0, /* 报文是否为自收发(回环) */</font>
- <font size="3"> };</font>
- <font size="3"> if (tawi_mode == TWAI_MODE_NO_ACK) /* 无应答模式 + (self = 1) 实现自发自收功能 */</font>
- <font size="3"> {</font>
- <font size="3"> tx_msg.self = 1; /* 1把消息发送到总线上且接收自己发送的消息也接收总线上消息 */</font>
- <font size="3"> } </font>
- <font size="3"> </font>
- <font size="3"> /* 正常模式+(self=0)实现把消息发送到总线上,但不接收自己发送的消息且不接收总线上的消息 */</font>
-
- <font size="3"> tx_msg.dlc_non_comp = 0; /* 0数据长度不大于8; 1数据长度大于8(非标) */</font>
- <font size="3"> tx_msg.identifier = id; /* 标准帧格式(11位ID)/扩展帧格式(29位ID) */</font>
- <font size="3"> tx_msg.data_length_code = len; /* 数据长度代码(字节为单位),数据帧数据符号大小 */</font>
- <font size="3"> memset(&tx_msg.data, 0, sizeof(tx_msg.data)); /* 发送数据,对远程帧无效 */</font>
- <font size="3"> for (int i = 0; i < len; i++) /* 复制数据到消息结构体 */</font>
- <font size="3"> {</font>
- <font size="3"> tx_msg.data</font><span style="font-size: medium;"><span style="font-style: italic;"><span style="font-style: normal;"> = msg; /* 数据(最多8个字节,跟data_length_code匹配) */
- }
-
- /* 发送消息 */
- ESP_ERROR_CHECK(twai_transmit(&tx_msg, pdMS_TO_TICKS(1000)));
- return ESP_OK;
- }</span></span></span>
复制代码 该函数的实现,主要调用twai_transmit函数。首先对tx_msg结构体变量成员进行赋值,把报文格式设置好。报文格式具体如下:标准帧、数据帧、单次发送。报文的自收发设置通过twai_mode全局变量决定,若TWAI操作模式为正常模式,就不接收自己报文,若是非应答模式可接收自己报文。报文ID和内容则是通过函数参数决定。
继续看一下TWAI查询接收数据的函数twai_receive_data,代码如下。
- /**
- * @brief TWAI接收数据查询
- * @note 接收数据格式固定为:标准ID,数据帧
- * @param id:要查询的 标准ID(11位)
- * @param buf:数据缓存区
- * @retval ESP_OK成功; ESP_FAIL失败
- */
- esp_err_t twai_receive_data(uint32_t id, uint8_t buf[])
- {
- twai_message_t rx_message;
- ESP_ERROR_CHECK(twai_receive(&rx_message, portMAX_DELAY));
-
- if (rx_message.identifier == id)
- {
- for (int i = 0; i < rx_message.data_length_code; i++)
- {
- buf<span style="font-style: italic;"><span style="font-style: normal;"> = rx_message.data</span><span style="font-style: normal;">;
- }
- }
- return ESP_OK;
- }</span></span>
复制代码 该函数的实现主要调用twai_receive函数。定义一个rx_message结构体变量,后面把它的地址作为twai_receive函数的参数,若接收到报文,后续便可以通过rx_message变量获取到。
2. CMakeLists.txt文件
本例程的功能实现主要依靠QMI8658A驱动。要在main函数中,成功调用QMI8658A文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- set(src_dirs
- LED
- KEY
- LCD
- MYIIC
- XL9555
- TWAI)
- set(include_dirs
- LED
- KEY
- LCD
- MYIIC
- XL9555
- TWAI)
- set(requires
- driver
- esp_lcd
- esp_common)
- idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
- component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码 3. main.c驱动代码
在main.c里面编写如下代码。
- /**
- * @brief CAN接收任务句柄
- * @param arg: 传入参数(未用到)
- * @retval 无
- */
- static void twai_receive_task(void *arg)
- {
- arg = arg;
- uint8_t ret = 0;
- uint8_t rx_canbuf[8] = {0};
- while (1)
- {
- ret = twai_receive_data(MSG_ID, rx_canbuf); /* 接收数据查询0x12报文 */
- if (ret == ESP_OK) /* 接收到有数据 */
- {
- lcd_fill(30, 270, 130, 310, WHITE);
- for (int i = 0; i < 8; i++)
- {
- if (i < 4)
- {
- lcd_show_xnum(30+i*32,270,rx_canbuf<span style="font-style: italic;"><span style="font-style: normal;">,3,16,0X80,BLUE);
- }
- else
- {
- lcd_show_xnum(30+(i-4)*32,290,rx_canbuf</span><span style="font-style: normal;">,3,16,0X80,BLUE);
- }
- }
- memset(rx_canbuf, 0, sizeof(rx_canbuf));
- }
- vTaskDelay(pdMS_TO_TICKS(10));
- }
- }
- void app_main(void)
- {
- esp_err_t ret;
- uint8_t key;
- uint8_t i = 0, t = 0;
- uint8_t cnt = 0;
- uint8_t canbuf[8] = {0};
- uint8_t twai_mode = 1;
- ret = nvs_flash_init(); /* 初始化NVS */
- if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
- {
- ESP_ERROR_CHECK(nvs_flash_erase());
- ESP_ERROR_CHECK(nvs_flash_init());
- }
- led_init(); /* LED初始化 */
- key_init(); /* KEY初始化 */
- myiic_init(); /* MYIIC初始化 */
- xl9555_init(); /* XL9555初始化 */
- lcd_init(); /* LCD屏初始化 */
- twai_init(TWAI_MODE_NO_ACK); /* TWAI初始化 */
- twai_mode = TWAI_MODE_NO_ACK; /* 标记TWAI模式 */
- lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);
- lcd_show_string(30, 70, 200, 16, 16, "TWAI TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "NO_ACK Mode ", RED);
- lcd_show_string(30, 130, 200, 16, 16, "K0:Send K1:NORMAL", RED);
- lcd_show_string(30, 150, 200, 16, 16, "K2:NO_ACK ", RED);
- lcd_show_string(30, 170, 200, 16, 16, "Count:", BLUE);
- lcd_show_string(30, 190, 200, 16, 16, "Send Data:", BLUE);
- lcd_show_string(30, 250, 200, 16, 16, "Receive Data:", BLUE);
- xTaskCreatePinnedToCore(twai_receive_task, "TWAI_RX_TASK", 4096, NULL, 9,
- &twai_receive_handle, tskNO_AFFINITY);
- while (1)
- {
- key = xl9555_key_scan(0);
- if (key == KEY0_PRES)
- {
- for (i = 0; i < 8; i++)
- {
- canbuf</span><span style="font-style: normal;"> = cnt + i;
- if (i < 4)
- {
- lcd_show_xnum(30+i*32, 210, canbuf</span><span style="font-style: normal;">, 3, 16, 0x80, BLUE);
- }
- else
- {
- lcd_show_xnum(30+(i-4)*32,230,canbuf</span><span style="font-style: normal;">, 3, 16, 0x80, BLUE);
- }
- }
- ret = twai_send_data(MSG_ID, canbuf, 8); /* ID=0x12, 发送8个字节 */
- if (ret)
- {
- lcd_show_string(30 + 80, 190, 200, 16, 16, "Failed", BLUE);
- }
- else
- {
- lcd_show_string(30 + 80, 190, 200, 16, 16, "OK ", BLUE);
- }
- }
- else if (key == KEY1_PRES)
- {
- if (twai_mode == TWAI_MODE_NO_ACK) /* 若当前是无应答模式 */
- {
- vTaskDelete(twai_receive_handle); /* 删除任务 */
- twai_init(TWAI_MODE_NORMAL); /* 切换到正常模式 */
- lcd_show_string(30, 110, 200, 16, 16, "NORMAL Mode ", RED);
- twai_mode = TWAI_MODE_NORMAL;
- xTaskCreatePinnedToCore(twai_receive_task, "TWAI_RX_TASK", 4096,
- NULL, 9, &twai_receive_handle, tskNO_AFFINITY);
- }
- }
- else if (key == KEY2_PRES)
- {
- if (twai_mode == TWAI_MODE_NORMAL) /* 若当前是正常模式 */
- {
- vTaskDelete(twai_receive_handle); /* 删除任务 */
- twai_init(TWAI_MODE_NO_ACK); /* 切换到无应答模式 */
- lcd_show_string(30, 110, 200, 16, 16, "NO_ACK Mode ", RED);
- twai_mode = TWAI_MODE_NO_ACK;
- xTaskCreatePinnedToCore(twai_receive_task, "TWAI_RX_TASK", 4096,
- NULL, 9, &twai_receive_handle, tskNO_AFFINITY);
- }
- }
- t++;
- vTaskDelay(pdMS_TO_TICKS(10));
- if (t == 20)
- {
- LED0_TOGGLE();
- t = 0;
- cnt++;
- lcd_show_xnum(30 + 48, 170, cnt, 3, 16, 0X80, BLUE);
- }
- }
- }</span></span>
复制代码 在app_main函数中,首先初始化所需的外设,注意TWAI此时默认配置为非应答模式,然后创建了TWAI的接收任务,后续在TWAI接收任务中查询是否接收到数据。在while循环中,通过检测按键去选择操作,若KEY0按下,发送报文消息;若KEY1按下,设置TWAI模式为正常模式;若KEY2按下,设置TWAI模式为非应答模式。以上操作会更新LCD屏的显示内容。
twai_receive_task函数便是TWAI接收任务,在函数中,主要就是调用twai_receive_data函数查询是否接收到数据,若接收到数据,就会在LCD上显示出来。
36.4 下载验证
将程序下载到开发板后(注意两个开发板都要下载这个代码),可以看到LED0不停闪烁,提示程序已经在运行了,可看到LCD显示的内容如图36.4.1所示:
图36.4.1 TWAI实验程序运行效果图
默认设置的是非应答模式,按下KEY0可以在LCD屏上看到自发自收的数据,如下图所示。
图36.4.2 TWAI非应答模式操作效果
这时候可以通过按下KEY1切换到正常模式,正常模式下,就必须连接两个开发板的CAN接口,然后便可以互发数据了,如图36.4.3和图36.4.4所示。
图36.4.3 TWAI正常模式发送数据
图36.4.4 TWAI正常模式接收数据
图36.4.3来自开发板A,发送了8个数据,图36.4.4来自开发板B,接收到了来自开发板A的8个数据。 |
|