1. CAN简介
CAN协议,全称为Controller Area Network(控制器局域网络),是一种广泛应用的串行通信协议,最初由德国BOSCH公司开发,并已成为国际标准。
低速CAN(ISO11519)通信速率 10~125Kbps,总线长度可达 1000 米。
高速CAN(ISO11898)通信速率 125Kbps~1Mbps,总线长度 ≤40 米。
定义:CAN协议是一种基于差分信号的异步串行通信协议,采用双绞线作为传输介质,具有高性能、高可靠性(汽车领域)和独特的设计特点。
特点:
多主控制:支持多主方式,即任何一个节点都可以在总线上发送数据,其他节点根据需要进行接收。当两个以上的节点同时开始发送数据时,会根据标识符(ID)决定优先级。
串口一般是一对一的、IIC可以多主多从 但是不好用、SPI是一主多从。
系统柔软性:与总线相连的单元没有类似于“地址”的信息,因此在总线上增加单元时,连接在总线上的其他单元的软硬件及应用层都不需要改变。
通信速度快、距离远:数据传输速率较高,标准速率为125kbps,扩展速率可达1Mbps,且通信距离远,最远可达10KM(速率低于5Kbps)。
错误检测与恢复:具有错误检测、错误通知和错误恢复功能,能够确保数据传输的可靠性。
故障封闭功能:能够判断出错误的类型,并将引起故障的单元从总线上隔离出去。
CAN协议因其高效、可靠的特点,被广泛应用于多个领域:
汽车领域:用于实现车辆中各个控制单元(ECU)之间的通信,如发动机控制、车身控制、传动系统控制等。
工业自动化:用于机器人、物流设备、生产线等各种设备之间的通信与控制。
航空航天:用于飞机中的各种子系统之间的数据通信,如飞行控制系统、引擎控制系统、仪表盘、通信系统等。
船舶领域:用于实现船舶各种设备的可靠通信,包括远程控制、安全监测、航程分析、巡回检测等系统。
智能家居:连接各种智能设备,如照明设备、空调系统、安防系统等,实现设备的互联互通。
智能医疗:连接医疗设备,如监护仪、呼吸机等,实现设备间的快速通信。
2. CAN物理层介绍
CAN网络通常由CAN控制器、CAN收发器和双绞线组成。
CAN控制器:负责处理数据的收发和协议转换。
CAN收发器:负责将控制器的数字信号转换为差分信号进行传输,同时也负责将总线上的差分信号转换为数字信号供控制器处理。
终端电阻;
在高速CAN总线的两端分别连接一个电阻,称为终端电阻。终端电阻的主要作用是匹配总线阻抗(把上下两根线的电压拉到同一水平线上),提高信号质量,减少回波反射。一般来说,终端电阻的阻值为120Ω。
2.1 总线拓扑图
2.2 电平标准
高速CAN使用差分信号进行数据传输,根据CAN_H和CAN_L上的电位差来判断总线电平。
显性电平表示逻辑 0,通常 CAN_H 和 CAN_L 有2V 的压差;
隐性电平表示逻辑 1,通常 CAN_H 和 CAN_L 有0V 的压差。
显性电平在通信中具有优先权,能够覆盖隐性电平,确保数据的正确传输。
隐性电平则作为总线的空闲或监听状态存在,等待有节点发送数据。
2.3 CAN控制器与收发器
收发器 TJA1050
这里的TX RX 不需要交叉接线 VCC5V GND
3. CAN协议层介绍
3.1 CAN帧种类介绍
CAN帧可以理解为一个由0101组成的数据包。
1.数据帧(Data Frame):数据帧是CAN总线上用于传输用户数据的帧,包括必要的帧头、标识符、控制位、数据长度代码、数据域、CRC校验码和应答域等部分,是CAN通信中最基本和最重要的帧类型。
2.遥控帧(Remote Frame):遥控帧用于向总线上的其他节点请求发送具有相同标识符的数据帧,它没有数据域,仅通过标识符来指定所需的数据。遥控帧的帧结构与数据帧相似,但缺少数据部分。
3.错误帧(Error Frame):当CAN总线上的任何节点检测到通信错误时,会发送错误帧来通知其他节点。错误帧包含错误标志和错误界定符,用于指示错误的存在和类型。
4.过载帧(Overload Frame):过载帧用于在连续的数据帧或远程帧之间提供额外的延时,以指示接收节点尚未准备好接收下一个帧。当接收节点因内部条件限制而无法立即接收数据时,会发送过载帧来请求发送节点暂停发送。
5.帧间隔(Interframe Space):帧间隔用于隔离数据帧与前面的帧,确保它们之间的时间间隔足够长,以避免总线上的冲突和数据丢失。帧间隔包括连续三个隐性位(间隔段)和可能存在的空闲段,用于将数据帧或远程帧与前面的帧分隔开来。
3.2 CAN数据帧介绍
数据帧由7段组成。数据帧又分为标准帧(CAN2.0A)和扩展帧(CAN2.0B),主要体现在仲裁段和控制段。
1. 帧起始(Frame Start):
功能:表示数据帧的开始。
特点:由一个显性位(Dominant Bit)构成,此时CAN_H为高电平(如3.5V),CAN_L为低电平(如1.5V),二者之间的电位差形成信号。
2. 仲裁段(Arbitration Field):
功能:确定发送优先级,并包含标识符ID(Identifier)用于唯一标识发送者和接收者之间的通信关系。
组成:标准数据帧的仲裁段由11位ID和1位RTR位(远程发送请求位)组成。RTR位用于区分数据帧(显性电平)和遥控帧(隐性电平)。
扩展数据帧中,还包含SRR位(Substituted Remote Request,替代的远程请求)和IDE位(Identifier Extension,标识符扩展)。SRR位用于指示发送方是否发送了远程请求帧,IDE
位用于指示标识符字段是否使用了扩展格式(29位)。
3. 控制段(Control Field):
功能:包含数据长度代码(DLC),用于定义数据帧中数据域的长度。IDE用于区分是标准格式0还是扩展格式1,标识符ID长度为11 / 29。r0是一个保留位,它的存在是为了将来协议扩展预留。
特点:DLC占4位,其取值范围为0到8个字节,表示数据帧中包含的数据字节数。最多64位。
4. 数据段(Data Field):
功能:包含要传输的数据,是数据帧的主体部分。
特点:数据域的长度可以根据DLC字段的值从0到8个字节不等,数据从最高位(MSB)开始传
输。
5. CRC段(CRC Field):
功能:用于检测数据帧的传输错误。
特点:CRC(循环冗余校验)是一种通过对数据进行计算生成的校验码,发送方在发送数据帧时会根据数据计算出CRC值,并将其添加到数据帧的CRC段中。接收方在接收到数据帧后会重新计算CRC值,并与接收到的CRC值进行比较(相同没有问题),以确认数据在传输过程中是否发生错误。
6. 应答段(ACK Field):
功能:用来确认数据帧的正常接收。
组成:由ACK槽(ACK Slot)和ACK界定符两个位构成。当接收节点成功解析了数据帧并确认无误后,会在ACK槽中发送一个显性位作为应答信号。
7. 帧结束(Frame End):
功能:表示数据帧的结束。
特点:由7个连续的隐性位构成,标志着数据帧的传输完成。
灰色的代表是显性信号(0),白色的是隐性信号(1),浅紫色是0/1。
当11位ID不够用的时候,就扩展成29位,原本的RTR位就移到后面了,原位置由SRR代替,标准下的IDE变r1,这样就有两个保留位了。后面都是一模一样得了。
标准的RTR永远为0,扩展的SRR永远为1,仲裁是一个位一个位检测的,所以说前 11 位基础 ID 相同时标准的优先级是一定比扩展格式更高的。
3.3 CAN位时序
CAN总线以“位同步”机制,实现对电平的正确采样。位数据都由四段组成:同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每段又由多个位时序Tq(可以配置)组成
所谓采样点是读取总线电平,并将读到的电平作为位值的点。位置在 PBS1 结束处。
采样只是每隔一段时间采一次,但是时间可能会产生误差,可能快也可能慢, 如何让SS段严格在波形边缘呢,就是通过同步。
CAN总线通过时钟同步机制来确保各个节点在通信过程中保持同步。时钟同步机制包括硬同步和再同步两种:
1.硬同步:
硬同步只在帧的起始位(SOF)处进行。
当接收节点检测到帧起始位的下降沿时,会将其与自身的位时间进行对齐,从而实现同步。
2.再同步:
再同步在帧的后续数据位中进行。
如果接收节点检测到数据位的跳变沿不在自身的同步段内,则会通过延长或缩短相位缓冲段的时间来调整自身的位时间,以重新获得同步。
再同步时,PBS1和PBS2中增加或者减少的时间被称为“再同步补偿宽度(SJW)”,其范围:1~4Tq。
3.4 CAN仲裁机制
CAN总线处于空闲状态,最先开始发送消息的单元获得发送权。(先到先得)
多个单元同时开始发送时,从仲裁段(报文ID)的第一位开始进行仲裁。仲裁原理如下:
1. 标识符优先级:
CAN总线中传输的数据帧的起始部分为数据的标识符(ID)。这个标识符不仅用于区分消息,还表示消息的优先级。在CAN 2.0标准中,标识符可以是11位或29位(对于扩展帧)。标识符的数
值越小,优先级越高。
例如,在11位标识符的情况下,ID为0x000的消息具有最高优先级,而ID为0x7FF的消息具有最低优先级。
2. 逐位仲裁:
当两个或两个以上的节点同时开始传送报文时,就会产生总线访问冲突。此时,各节点会按照标识符的位顺序逐位进行仲裁。在仲裁过程中,每个节点都会将自己发送的电平与总线上的电平进行比较。如果电平相同,则节点继续发送下一位;如果电平不同,则优先级低的节点停止发送,而优先级高的节点继续发送。
这种仲裁方式是非破坏性的,即优先级低的节点在仲裁过程中不会破坏总线上已经存在的数据。
3. 显性电平优先:
在CAN总线上,显性电平(逻辑0)的优先级高于隐性电平(逻辑1)。因此,在仲裁过程中,如果某个节点发送的是隐性电平,但检测到总线上存在显性电平,那么该节点就会知道有更高优先
级的消息正在发送,并主动停止发送。
一旦仲裁结束,优先级最高的节点将获得总线控制权并开始传输数据。其他节点则成为接收节点并监听总线上的数据,并会自动检测总线空闲,在第一时间再次尝试发送。
4. STM32 CAN控制器介绍
4.1 CAN控制器介绍
STM32的bxCAN,即基本扩展CAN(Basic Extend CAN),是STM32微控制器系列中集成的CAN控制器模块。
1. 协议支持:支持CAN协议2.0A(只认识标准格式)和2.0B的主动模式(都认识)。B的被动会扔掉扩展格式 也相当于不认识。
2. 高波特率:波特率最高可达1兆位/秒。 波特率一致双方才可以进行通信。
3. 时间触发通信:支持时间触发通信功能,CAN的硬件内部定时器可以在TX/RX的帧起始位的采样点位置生成时间戳。
4. 发送功能:
具有3个发送邮箱(可以认为是缓存),发送报文的优先级特性可软件配置。
记录发送SOF(Start Of Frame,帧起始)时刻的时间。
5. 接收功能:
具有3级深度的2个FIFO(First In First Out,先进先出队列),每个FIFO(有三个邮箱)都可以存放3个完整的报文,完全由硬件管理。 共有14个位宽可变的过滤器组(不想要的报文过滤掉,部分STM32型号可能支持更多),由整个CAN共享,用于筛选有效报文。
记录接收SOF时刻的时间。
支持禁止自动重传模式。
4.2 CAN控制器模式
CAN控制器的工作模式有三种:初始化模式、正常模式和睡眠模式。
睡眠模式:在睡眠模式下,CAN控制器的时钟停止,以降低功耗。但软件仍然可以访问邮箱寄存器。
初始化模式:在初始化模式下,禁止报文的接收和发送,并且CANTX引脚输出隐性位(高电平)。此时,可以对CAN控制器的相关寄存器进行配置,如位时间特性(CAN_BTR)和控制(CAN_MCR)等。
正常模式:作为总线的正常节点,可以向总线发送或接收数据。
CAN控制器的测试模式有三种:静默模式、环回模式和环回静默模式,主要用于特定的测试或调试目的,以确保CAN控制器的功能正常。
1. 静默模式(相当于只能收 不能发)
特点:
在静默模式下,CAN控制器可以正常地接收数据帧和远程帧,但只能发出1隐性位,而不能真正发送报文。
这意味着,虽然CAN控制器在尝试发送数据,但实际上它并没有在CAN总线上产生任何显性位,因此不会对总线上的其他节点产生影响。
应用场景:
静默模式通常用于分析CAN总线的活动,而不会对总线上的其他通信造成干扰。
开发人员可以使用此模式来观察总线上的数据流,而无需担心他们的测试设备会发送出不必要的报文。
2. 环回模式
特点:
在环回模式下,CAN控制器会把发送的报文当作接收的报文并保存(如果可以通过接收过滤)。
这意味着,当CAN控制器发送一个报文时,它会立即在自己的接收缓冲区中看到这个报文,就像它是从总线上接收到的一样,但可能对外部总线产生干扰。
应用场景:
环回模式通常用于自测试,以验证CAN控制器的发送和接收功能是否正常。
通过发送一个报文并检查它是否被正确接收,开发人员可以确保CAN控制器的硬件和固件都按预期工作。
3. 环回静默模式
特点:
环回静默模式结合了静默模式和环回模式的特点。是环回模式的“安全版”。
在该模式下,CANRX引脚与CAN总线断开,同时CANTX引脚被驱动到隐性位状态。
这意味着,虽然CAN控制器在尝试发送报文,但它实际上并没有在CAN总线上产生任何显性位,并且它会将发送的报文视为接收到的报文。
应用场景:
环回静默模式通常用于“热自测试”,即可以在不影响CANTX和CANRX所连接的整个CAN系统的情况下进行测试。
这种模式允许开发人员在不干扰总线上其他通信的情况下,验证CAN控制器的发送和接收功能。
4.3CAN控制器框图
CAN控制内核:包含各种控制/状态/配置寄存器,用于配置CAN控制器的模式、波特率等参数。
发送邮箱(Transmit Mailbox):用来缓存待发送的CAN报文。STM32等微控制器通常具有多个发送邮箱(如3个),以支持同时缓存多个报文。
接收FIFO(First In First Out):缓存接收到的有效CAN报文。CAN控制器通常具有多个接收FIFO(如2个),以提高接收效率。
接收过滤器(Receive Filter):筛选接收到的CAN报文,只将符合特定条件的报文保存到接收FIFO中。这有助于减少CPU的处理负担,提高系统的响应速度。c8t6这块板子上是有14个的。
一个芯片里的过滤器都是共享的。
之前说过CAN这个协议,里面每个设备都可以做主机,是一个多主机的通信协议,所以没有所谓的主机从机的关系,只不过CAN2是在内部,要读写控制器的访问的话,要通过CAN1去间接的访问。只不过是在结构上看起来像主从的关系而已。实际上是平等的关系 .
发送处理过程:
首先选择一个空置的邮箱,数据塞入进去之后,就进入了挂号的状态,接下来他就等待自己成为最高优先级的邮箱,因为三个邮箱不一定说你塞进去的就是最高级的,需要等待。之后变成了预定的状态,等待总线是空闲的状态。总线空闲就发送成功之后回到了空置状态。但发送失败也会变成空置,虽然名字叫的一样,但是实际上与其他两个还有区别的。
接收处理过程:
先选择一个FIFO,一开始是空的状态,收到一个报文 就放到第一层中,变成挂号1的状态,第二个收的放第二层,挂号2的状态。
如果三个已经满了的话,在收到一个报文,就会溢出,也就是说后面发出的报文就不接收了。
这个时候就该CPU去读取这个数据了,先进后出的一个模式,所以说 读的顺序实际是3-2-1。
有效报文指的是(数据帧直到EOF段的最后一位都没有错误),且通过过滤器组对标识符过滤。
接收过滤器
当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用CPU。过滤器的存在,选择性接收有效报文,减轻系统负担。
STM32的CAN控制器支持配置过滤器组,每个过滤器组包含2个32位的寄存器CAN_FxR1和CAN_FxR2, 用于存储要筛选的ID或掩码。 对于STM32F103C8T6, 如果只有一个CAN控制器, 则可以配置14个过滤器组, 对应的编号为0到13。
过滤器可以配置为不同的位宽,以适应不同长度的CAN ID。常见的位宽包括16位(用于标准帧)和32位(用于扩展帧)。
选择模式可设置屏蔽位模式或标识符列表模式,寄存器内容的功能就有所区别。
屏蔽位模式,可以选择出一组符合条件的报文。寄存器内容功能:是否符合条件。(指定几位)
标识符列表模式,可以选择出几个特定ID的报文。寄存器内容功能就是标识符本身。(全指定)
4.4 CAN控制器位时序
SS----PTS+PBS1------PBS2 。 波特率在二进制的条件下跟比特率是一样的。
比特率就是,一秒的时间可以传输多少位。 倒数就是波特率。
SS段固定是一个Tq,BS1和BS2都是可以通过寄存器进行配置的,Tq也是可以配置的,
这里的Tpclk大小为36M,因为我们的bxCAN是挂载到ABP1里面。所以就是1/36M。
设TS1=8、TS2=7、BRP=3,波特率 = 36000 / [( 9 + 8 + 1 ) * 4] = 500Kbps。
注意:通信双方波特率需要一致才能通信成功。
5. CAN寄存器及库函数介绍
CAN的寄存器主要分为3类:
控制和状态寄存器、邮箱寄存器、过滤器寄存器。
控制和状态寄存器
主控制器主要关注的是第0位 INRQ这一位。
那么如何看总线达到了空闲的状态呢,就看里面有没有连续的11个隐性位,就做好了收发的准备。
发送状态寄存器,只要关注IME0-1-2这三位。
位时序寄存器,配置上面说过的那个公式里的数值。BRP TS1 TS2 SJW(跳跃1-4Tq)
邮箱寄存器,
除了下述例外,发送和接收邮箱几乎一样:
1.CAN_RDTxR寄存器的FMI域;
2.接收邮箱是只读的;
3.发送邮箱只有在它为空时才是可写的,CAN TSR寄存器的相应TME位为’1,表示发送邮箱为空。
共有3个发送邮箱和2个接收邮箱。每个接收邮箱为3级深度的FIFO,并且只能访问FIFO中最先收到的报文,每个邮箱包含4个寄存器。
发送邮箱标识符寄存器,有3个 x=0,1,2
首先要确定数据帧是标准格式STID还是扩展格式EXID。位数不同。
发送邮箱数据长度和时间戳寄存器,只需要关注一位,DLC 发送数据的长度。
发送邮箱低字节数据寄存器,
因为我们最多发送的数据是64位的 也就是八个字节
一个寄存器最多保存四个字节,所以这里分成了两个寄存器 分别存低和高。
接下来看接收的寄存器。
依旧是DLC位,数据的长度。
也是有高低两个数据寄存器的。
过滤器寄存器
主要关注的是第0位,确定工作模式。
像c8t6之前说过,过滤器组只有14个 所以就是0-13位。每个过滤器组配置为屏蔽位或列表模式。
上面是确定工作模式 ,这里就是确定位宽。
关联寄存器,用于确定接收到的报文通过过滤之后,进入FIFO0还是1,。
用到那个 就对应激活那个。
每个过滤器组 都包含两个32位的寄存器CAN_FxR1和CAN_FxR2, 用于存储要筛选的ID或掩码。
库函数
HAL_CAN_Init 初始化
HAL_CAN_ConfigFilter 配置过滤器
HAL_CAN_Start 开始正常工作
HAL_CAN_AddTxMessage 发送数据
HAL_CAN_GetRxMessage 接收数据
HAL_CAN_GetTxMailboxesFreeLevel 等待发送完成
HAL_CAN_GetRxFifoFillLevel 等待接收完成
6. CAN基本驱动流程
小实验:CAN收发实验
实验目的
1. 使用回环模式实现自发自收;
2. 两个CAN设备实现收发。
硬件清单
TJA1050
上官二号
ST-Link
USB转TTL
老规矩 创建CAN的文件夹,然后我们这里也会涉及按下key1开始发送数据。“品”魔法棒,can驱动
#include "can.h"后编译 ,
#include "can.h" #include "stdio.h" CAN_HandleTypeDef can_handle = {0}; //全局变量 结构体(句柄) 初始为0 void can_init(void) { can_handle.Instance = CAN1; //基地址 can_handle.Init.Mode = CAN_MODE_NORMAL; //工作模式(正常模式)CAN_MODE_LOOPBACK回环 can_handle.Init.Prescaler = 4; //预分频器 配置Tq 实际上是设定(BRP+1)的值 can_handle.Init.TimeSeg1 = CAN_BS1_9TQ; //配置BS1,当时设的TS1为8 要+1 can_handle.Init.TimeSeg2 = CAN_BS2_8TQ; //同样 7+1=8 can_handle.Init.SyncJumpWidth = CAN_SJW_1TQ; //跳跃1个Tq can_handle.Init.AutoBusOff = DISABLE; /* 禁止自动离线管理 */ can_handle.Init.AutoRetransmission = DISABLE; /* 禁止自动重发 */ can_handle.Init.AutoWakeUp = DISABLE; /* 禁止自动唤醒 */ can_handle.Init.ReceiveFifoLocked = DISABLE; /* 禁止接收FIFO锁定 */ can_handle.Init.TimeTriggeredMode = DISABLE; /* 禁止时间触发通信模式 */ can_handle.Init.TransmitFifoPriority = DISABLE; /* 禁止发送FIFO优先级 */ HAL_CAN_Init(&can_handle); //初始化 参数为句柄 CAN_FilterTypeDef can_filterconfig = {0}; //参数2的结构体句柄 初始0 can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK; //工作模式掩码(还有列表) can_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT;//宽度 16 or 32 拓展帧选32 can_filterconfig.FilterIdHigh = 0; //不过滤任何东西确保都能收到 都设置0 can_filterconfig.FilterIdLow = 0; can_filterconfig.FilterMaskIdHigh = 0; can_filterconfig.FilterMaskIdLow = 0; can_filterconfig.FilterBank = 0; //0-13个寄存器 指定用哪个 can_filterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; //过滤之后进入到FIFO0 can_filterconfig.FilterActivation = CAN_FILTER_ENABLE; //过滤器使能 can_filterconfig.SlaveStartFilterBank = 14; //对于单can无意义 但实际意义是低于14的过滤器组归CAN1,等于或大于的归CAN2 HAL_CAN_ConfigFilter(&can_handle, &can_filterconfig); //配置过滤器 HAL_CAN_Start(&can_handle); //开始工作 } void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan) //配置时钟 gpio 中断 { __HAL_RCC_CAN1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef gpio_initstruct;//查手册 CAN的TX对应PA12,RX对应PA11 gpio_initstruct.Pin = GPIO_PIN_12; gpio_initstruct.Mode = GPIO_MODE_AF_PP; //复用推挽输出 gpio_initstruct.Pull = GPIO_PULLUP; gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &gpio_initstruct); gpio_initstruct.Pin = GPIO_PIN_11; gpio_initstruct.Mode = GPIO_MODE_INPUT; //复用输入 HAL_GPIO_Init(GPIOA, &gpio_initstruct); } //发送的id因为用的是扩展模式29位,具体的data,长度 void can_send_data(uint32_t id, uint8_t *buf, uint8_t len) { CAN_TxHeaderTypeDef tx_header = {0}; //第二个参数-结构体的定义。 uint32_t tx_mail = CAN_TX_MAILBOX0; //邮箱定义 tx_header.ExtId = id; //ExtId 扩展帧 和StdId 标准帧 配置一个即可 tx_header.DLC = len; //长度 tx_header.IDE = CAN_ID_EXT; //扩展帧 tx_header.RTR = CAN_RTR_DATA; //数据帧还是遥控帧 HAL_CAN_AddTxMessage(&can_handle, &tx_header, buf, &tx_mail); // 句柄、 结构体、 具体data、指定发的邮箱 while(HAL_CAN_GetTxMailboxesFreeLevel(&can_handle) != 3);//不等于3 就一直空循环 // 会返回当前空闲的发送邮箱数量,等待发完 uint8_t i = 0; printf("发送数据:\r\n"); //stdio.h for(i = 0; i < len; i++) printf("%X ", buf[i]); //%X以十六进制大写形式输出 printf("\r\n"); } //返回值 接收了多少个字节 uint8_t can_receive_data(uint8_t *buf) //接收的数据通过指针的类型传出 {//只需要定义一个空的结构体变量,把地址传进去,函数会自己把收到的 ID、帧类型、数据长度填进去。 CAN_RxHeaderTypeDef rx_header = {0};//只需要定义一个空的结构体变量,函数会自己填进去。 //返回 FIFO0 中已填充的报文数量。 if(HAL_CAN_GetRxFifoFillLevel(&can_handle, CAN_RX_FIFO0) == 0)//判断FIFO0是否为空 return 0; //句柄、指定哪个在FIFO取、数据的头、具体的数据 HAL_CAN_GetRxMessage(&can_handle, CAN_RX_FIFO0, &rx_header, buf); uint8_t i = 0; printf("接收数据:\r\n"); for(i = 0; i < rx_header.DLC; i++) printf("%X ", buf[i]); printf("\r\n"); return rx_header.DLC; }#ifndef __CAN_H__ #define __CAN_H__ #include "sys.h" void can_init(void); void can_send_data(uint32_t id, uint8_t *buf, uint8_t len); uint8_t can_receive_data(uint8_t *buf); #endif#include "sys.h" #include "delay.h" #include "led.h" #include "uart1.h" #include "can.h" #include "key.h" uint8_t data_send[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; uint8_t data_receive[8]; int main(void) { HAL_Init(); /* 初始化HAL库 */ stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ led_init(); /* 初始化LED灯 */ uart1_init(115200); can_init(); key_init(); printf("hello world!\r\n"); uint8_t i = 0; while(1) { if(key_scan() == 1) { for(i = 0; i < 8; i++) data_send[i]++; can_send_data(0x12345678, data_send, 8);// 指定发送的数据 长度 } can_receive_data(data_receive); } }Init当中 设置LOOPBACK环回的时候 实现的是自发自收,如果要接到总线上去的时候,只需要将这个改成正常工作模式NORMAL就可以。
如果要多个节点 就像上面图的那样接线就可以,注意不要交叉接线。