当前位置: 首页 > article >正文

深入理解CAN协议

一、CAN简介

1.0 前期回顾

名称引脚双工时钟设备应用场景电平
UARTTX、RX全双工异步点对点两个设备互相通信单端
I2CSCL、SDA半双工同步多设备一个主控外挂多个模块单端
SPISCK、MOSI、MISO、SS全双工同步多设备一个主控外挂多个模块单端
CANCAN_H、CAN_L半双工异步多设备多个主控互相通信差分

UART协议针对主设备之间的通信,但是只能两个,为了实现多设备之间通信出现了IIC,但是IIC是主设备和从设备之间通信,无法实现多个主设备之间通信。为了解决多个主设备 之间的通信出现了CAN协议。

由此可见,CAN约等于UART+IIC。

1.1 特征

  • 两根通信线(CAN_H,CAN_L)

  • 差分信号通信,抗干扰能力强

  • 异步,无需时钟线,通信速率由设备各自约定

  • 半双工,可挂载多设备,多设备同时发送数据时通过仲裁判断先后顺序

  • 报文ID,用于区分消息功能,同时决定优先级

  • 11位/29位报文ID,用于区分消息功能,同时决定优先级

  • 可配置1-8字节的有效载荷

  • 可实现广播式和请求式两种传输方式

  • 应答、CRC校验、位填充、位同步、错误处理等特性

二、物理层

一个CAN节点的硬件部分一般由CAN控制器CAN收发器两个部分组成。

CAN控制器负责CAN总线的逻辑控制,实现CAN传输协议;

CAN收发器主要负责MCU逻辑电平与CAN总线电平之间的转换

2.1 总线结构

  • 每个设备通过CAN收发器挂载到CAN总线网络上

  • CAN控制器引出的TX和RX与CAN收发器相连,CAN收发器引出的CAN_H和CAN_L分别于总线的CAN_H和CAN_L相连。

  • 高速的CAN使用闭环网络,CAN_H和CAN_L两端添加120Ω的终端电阻

  • 低速的CAN使用开环网络,CAN_H和CAN_L其中一端添加2.2KΩ的终端电阻

两个120Ω是为了:防止回波反射;

在没有操作时,将电压收紧至同一水平(默认1),和IIC一样。

2.2 CAN电平标准

  • CAN总线采用差分信号,即两线电压差(VCAN_H-VCAN_L)传输数据位

  • 以差分信号传输信息具有抗干扰能力强,能有效抑制外部电磁干扰等优点

  • 高速CAN规定:

    电压差为0V时表示逻辑1(隐性电平)

    电压差为2V时表示逻辑0(显性电平),类似 I2C 总线的“线与”特性将使它处于显性电平状态,即可以认为显性具有优先的意味。

    这样压差为0 ,需要回环

  • 低速CAN规定:

    电压差为-1.5V时表示逻辑1(隐性电平)

    电压差为3V时表示逻辑0(显性电平) ​ 这样压差不为0 ,不需要回环

由上图可知:

  • 发送0时,将两跟线拉开

  • 发送1时,无需操作,两根线在电压作用下收缩。

2.3 CAN收发器

接收部分

  • CAN_H和CAN_L电平接收

  • RECEIVER:接收器,始终检查电压差。有电压差输出1,无输出0。

  • 电平反向:压差为1,输出0;压差为0,输出1。

    输出1示意图:

    输出0示意图:

发送部分

  • 200uaVCC:上拉电阻,TXD悬空了,默认输入1,防止输入引脚电平不确定造成误操作。

  • TXD TIME-OUT:TXD异常,始终输入0,始终拉紧线,整条会崩坏。如果设备异常,等一会会自动释放,防止出错。

  • 发送1:DRIVER关闭上下两个导管,相当于不对总线进行任何操作,在外面终端电阻收缩下,显示隐性电平(1)

  • 发送0:DRIVER打开两个导管,相当于拉紧两根总线,显示显性电平(0)

由此可知:

  • 发送0时,将两跟线拉开

  • 发送1时,无需操作,两根线在电压作用下收缩。

2.4 物理层总图

解决问题:

  • 规定了硬件电路如何接

  • 电平定义

  • 如何发送显性电平0和隐性电平1

三、数据层

3.1 总线帧格式

帧类型用途
数据帧发送设备主动发送数据(广播式)
遥控帧接收设备主动请求数据(请求式)
错误帧某个设备检测出错误时向其他设备通知错误
过载帧接收设备通知其尚未做好准备
帧间隔用于将数据帧及遥控帧与前面的帧分开

3.1.1 数据帧

  • SOF:帧起始,显性电平,表示后面一段波形为传输的数据位,其他节点通过帧起始信号的电平跳变沿来进行硬同步

  • ID :标识符,区别功能,同时决定优先级

  • RTR:远程请求位,区分数据帧和遥控帧。远程帧为1,数据帧为0。

  • IDE:扩展标志位,区分标准格式和扩展格式

  • SRR:替代RTR,协议升级时留下的无意义位。由于扩展帧中的 SRR 位为隐性位,RTR 在数据帧为显性位,所以在两个 ID 相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。

  • r0/r1:保留位,为后续协议升级留下空间

  • DLC:数据长度,指示数据段有几个字节

  • Data:数据段的1-8个字节有效数据

  • CRC:循环冗余校验,校验数据是否正确

  • ACK:应答位,判断数据有没有被接收方接收,发送方发送隐性电平,接收方发送显性电平

    发送方释放总线,接收方拉开总线,表示接收方对发送方的应答

  • CRC/ACK界定符:为应答位前后发送方和接收方释放总线留下时间

  • EOF:帧结束,表示数据位已经传输完毕,帧结束段由发送节点发送的 7 个隐性位表示结束。

3.1.2 遥控帧

  • 遥控帧无数据段,RTR为隐性电平1,其他部分与数据帧相同。

  • 当遥控帧和数据帧同时发送时,数据帧会优先发送

    原因:它们的远程传输请求位(RTR)的电平不同。数据帧的RTR位为显性电平(逻辑0),而遥控帧的RTR位为隐性电平(逻辑1)。在CAN总线的仲裁机制中,显性电平具有比隐性电平更高的优先级,这是因为CAN总线使用“线与”逻辑,即当多个节点同时发送数据时,总线上的电平状态由所有节点发送的电平进行逻辑“与”操作决定。如果任一节点发送显性电平,总线状态即显示为显性电平,从而覆盖隐性电平。因此,数据帧由于其RTR位为显性电平,能够获得优先发送的权利。

3.1.3 错误帧

  • 总线上所有设备都会监督总线的数据,一旦发现“位错误”或“填充错误”或“CRC错误”或“格式错误”或“应答错误”,这些设备便会发出错误帧来破坏数据,同时终止当前的发送设备。

主动错误:6个0,拉开总线

被动错误:6个1,不去喷总线,不会破坏别人的数据,会破坏自己的。

3.1.4 过载帧

  • 当接收方收到大量数据而无法处理时,其可以发出过载帧,延缓发送方的数据发送,以平衡总线负载,避免数据丢失。

3.1.5 帧间隔

  • 将数据帧和远程帧与前面的帧分离开

3.2 位填充

  • 规则:发送方每发送5个相同电平后,自动追加一个相反电平的填充位,接收方检测到填充位,会自动移除填充位,恢复原始数据

  • 例如:

    即将发送: 100000110 10000011110 0111111111110

    实际发送: 1000001110 1000001111100 011111011111010

    实际接收: 1000001110 1000001111100 011111011111010

    移除填充后:100000110 10000011110 0111111111110

  • 作用:

    • 添加波形定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机

    • 将正常数据流与“错误帧”和“过载帧”区分开,标志“错误帧”和“过载帧”的特异性

    • 保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲

  • 位填充规则主要应用于数据帧和远程帧的某些部分,而不是所有段。具体来说,位填充适用于以下领域:

    1. 帧起始(SOF):帧起始位是隐性到显性的跳变,用于同步。

    2. 仲裁域(Arbitration Field):包括标识符和RTR位,用于确定帧的优先级。

    3. 控制域(Control Field):包括数据长度码(DLC),用于指示随后数据字段的长度。

    4. 数据域(Data Field):实际传输的数据,可以是0到8字节。

    5. CRC序列(CRC Sequence):循环冗余校验,用于错误检测。

    位填充不适用于以下领域:

    • CRC界定符(CRC Delimiter):一个隐性位,用于界定CRC序列和应答域。

    • 应答域(ACK Field):包括应答位和应答界定符,用于确认数据帧的接收。

    • 帧结尾(EOF):由7个隐性位组成,标志着帧的结束。

3.3 接收方数据采样

  • CAN总线没有时钟线,总线上的所有设备通过约定波特率的方式确定每一个数据位的时长

  • 发送方以约定的位时长每隔固定时间输出一个数据位

  • 接收方以约定的位时长每隔固定时间采样总线的电平,输入一个数据位

  • 理想状态下,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近

接收方数据采样遇到的问题

  • 接收方以约定的位时长进行采样,但是采样点没有对齐数据位中心附近

  • 接收方刚开始采样正确,但是时钟有误差,随着误差积累,采样点逐渐偏离

3.4 位时序

3.4.1 位时序分解

  • 为了灵活调整每个采样点的位置,使采样点对齐数据位中心附近,CAN总线对每一个数据位的时长进行了更细的划分,分为同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每个段又由若干个最小时间单位(Tq)构成

SS 段 (SYNC SEG)

同步段,若通讯节点检测到总线上信号的跳变沿被包含在 SS 段的范围之内,则表示节点与总线的时序是同步的,当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电平。SS 段的大小固定为 1Tq。

PTS 段 (PROP SEG)

传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上输入比较器延时和输出驱动器延时总和的两倍。PTS 段的大小可以为 1~8Tq。

PBS1 段 (PHASE SEG1),

相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。PBS1 段的初始大小可以为 1~8Tq。

PBS2 段 (PHASE SEG2)

另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。PBS2 段的初始大小可以为 2~8Tq。

PBS1和PBS2之间用来确定采样点

3.4.2 波特率计算

波特率=1/一个数据位的时长=1/(Tss+Tpts+Tpbs1+Tpbs2)

例如:

SS=1Tq,PTS=3Tq,PBS1=3Tq,PBS2=3Tq

Tq=0.5us

波特率=1/(0.5+1.5+1.5+1.5)=200kps

3.4.3 同步过程分析

  • 硬同步

    • 每个设备都有一个位时序计时周期,当某个设备(发送方)率先发送报文,其他所有设备(接收方)收到SOF的下降沿时,接收方会将自己的位时序计时周期拨到SS段的位置,与发送方的位时序计时周期保持同步

    • 硬同步只在帧的第一个下降沿(SOF下降沿)有效

    • 经过硬同步后,若发送方和接收方的时钟没有误差,则后续所有数据位的采样点必然都会对齐数据位中心附近

image.png

可以看到当总线出现帧起始信号时,某节点检测到总线的帧起始信号不在节点内部时序的 SS 段范围,所以判断它自己的内部时序与总线不同步,因而这个状态的采样点采集得的数据是不正确的。所以节点以硬同步的方式调整,把自己的位时序中的 SS 段平移至总线出现下降沿的部分,获得同步,同步后采样点就可以采集得正确数据了。

  • 再同步

    • 若发送方或接收方的时钟有误差,随着误差积累,数据位边沿逐渐偏离SS段则此时接收方根据再同步补偿宽度值(SJW)通过加长PBS1段,或缩短PBS2段,以调整同步

    • 再同步可以发生在第一个下降沿之后的每个数据位跳变边沿

节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前 2Tq,这时控制器在下一个位时序中的 PBS1 段增加 2Tq 的时间长度,使得节点与总线时序重新同步。

节点从总线的边沿跳变中,检测到它的时序比总线的时序相对滞后 2Tq,这时控制器在前一个位时序中的 PBS2 段减少 2Tq 的时间长度,获得同步。

3.5 仲裁

3.5.1 多设备同时发送遇到的问题

  • CAN总线只有一对差分信号线,同一时间只能有一个设备操作总线发送数据,若多个设备同时有发送需求,该如何分配总线资源?

  • 解决问题的思路:制定资源分配规则,依次满足多个设备的发送需求,确保同一时间只有一个设备操作总线

3.5.2 资源分配规则1-先到先得

  • 若当前已经有设备正在操作总线发送数据帧/遥控帧,则其他任何设备不能再同时发送数据帧/遥控帧(可以发送错误帧/过载帧破坏当前数据)

  • 任何设备检测到连续11个隐性电平,即认为总线空闲,只有在总线空闲时设备才能发送数据帧/遥控帧

  • 一旦有设备正在发送数据帧/遥控帧,总线就会变为活跃状态,必然不会出现连续11个隐性电平,其他设备自然也不会破坏当前发送

  • 若总线活跃状态其他设备有发送需求,则需要等待总线变为空闲,才能执行发送需求

3.5.3 资源分配规则2-非破坏性仲裁

  • 若多个设备的发送需求同时到来或因等待而同时到来,则CAN总线协议会根据ID号(仲裁段)进行非破坏性仲裁,ID号小的(优先级高)取到总线控制权,ID号大的(优先级低)仲裁失利后将转入接收状态,等待下一次总线空闲时再尝试发送

  • 实现非破坏性仲裁需要两个要求:

    • 线与特性:总线上任何一个设备发送显性电平0时,总线就会呈现显性电平0状态,只有当所有设备都发送隐性电平1时,总线才呈现隐性电平1状态,即:0&X&X=0,1&1&1=1

    • 回读机制:每个设备发出一个数据位后,都会读回总线当前的电平状态以确认自己发出的电平是否被真实地发送出去了,根据线与特性,发出0读回必然是0,发出1读回不一定是1

3.6 错误种类

3.6.1 位错误

  • 位错误由向总线上输出数据帧、遥控帧、错误帧、过载帧的单元和输出 ACK 的单元、输出错误的单元来检测。

  • 在仲裁段输出隐性电平,但检测出显性电平时,将被视为仲裁失利,而不是位错误。

  • 在仲裁段作为填充位输出隐性电平时,但检测出显性电平时,将不视为位错误,而是填充错误。

  • 发送单元在 ACK 段输出隐性电平,但检测到显性电平时,将被判断为其它单元的 ACK 应答,而非位错误。

  • 输出被动错误标志(6 个位隐性位)但检测出显性电平时,将遵从错误标志的结束条件,等待检测出连续相同 6 个位的值(显性或隐

3.6.2 错误状态

  • 主动错误状态的设备正常参与通信并在检测到错误时发出主动错误帧

  • 被动错误状态的设备正常参与通信但检测到错误时只能发出被动错误帧处于被动错误状态的单元虽能参加总线通信,但为不妨碍其它单元通信,接收时不能积极地发送错误通知。

    处于被动错误状态的单元即使检测出错误,而其它处于主动错误状态的单元如果没发现错误,整个总线也被认为是没有错误的。

  • 总线关闭状态的设备不能参与通信 每个设备内部管理一个TEC和REC,根据TEC和REC的值确定自己的状态

四、CAN 外设介绍

  • STM32内置bxCAN外设(CAN控制器),支持CAN2.0A和2.0B,可以自动发送CAN报文和按照过滤器自动接收指定CAN报文,程序只需处理报文数据而无需关注总线的电平细节。

  • 波特率最高可达1兆位/秒

  • 3个可配置优先级的发送邮箱

  • 2个3级深度的接收FIFO

  • 28个过滤器组(互联型28个)

  • 时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置、发送优先级可配置、双CAN模式

  • 不支持使用 DMA 进行数据收发

4.1 CAN网络拓扑结构

4.2 CAN框图

4.2.1 CAN基本结构

4.2.2 工作模式

  • 初始化:用于配置CAN外设,禁止报文的接收和发送

  • 正常:配置CAN外设后进入正常模式,以便正常接收和发送报文

  • 睡眠:低功耗,CAN外设是时钟模式停止,可使用软件唤醒或者硬件自动唤醒

  • AMUM:置1,自动唤醒,一旦检测到CAN总线活动,硬件就自动清零SLEEP,唤醒CAN外设。

    置0,手动唤醒,软件清零SLEEP,唤醒CAN外设

4.2.3 测试模式

  • 静默模式:可以接收总线的数据和自己发送的信息,一般用于监测。分析总线上的流量,但不会因为发送显性位而影响总线。

  • 静默模式下,他自己的输出端的逻辑0数据会直接传输到自己的接收端,逻辑1可以被发送到总线。所以他不能向总线发送显性位,只能发送隐性位。接收端可以从总线接收内容。

  • 环回模式:输入端只能接收自己发送端的内容,不能接收来自总线上的内容。发送端的内容可以传输到总线上,同时也可以传输到自己的输入端。可以进行自检,使用总线来检测自己发送内。

  • 静默环回模式:是以上两种模式的结合。自己的输出端的所有内容都直接传输到自己的输入端,并且不会向总线发送显性位影响到总线。不能通过总线来监测自己发送内容。接收端只能接收自己发送端的内容,不能接收来自总线上的内容。在“热自检”时使用,即自我检查的时候不会影响总线。

4.2.4 位时序及波特率

stm32外设定义的位时序与前面的CAN标准时序有一点区别。

stm32的CAN外设的位时序可以把他的BS1段理解为PTS段和PBS1段结合在一起,BS2相当于PBS2段。

了解位时序后,我们就可以配置波特率了。通过配置位时序寄存器 CAN_BTR 的 TS1[3:0] 及

TS2[2:0] 寄存器位设定 BS1 及 BS2 段的长度后,我们就可以确定每个 CAN 数据位的时间:

BS1 段时间:TS1=Tq x (TS1[3:0] + 1),

BS2 段时间:TS2= Tq x (TS2[2:0] + 1),

一个数据位的时间:T1bit =1Tq+TS1+TS2=1+ (TS1[3:0] + 1)+ (TS2[2:0] + 1)= N Tq

其中单个时间片的长度 Tq 与 CAN 外设的所挂载的时钟总线及分频器配置有关,CAN1 和 CAN2外设都是挂载在 APB1 总线上的,而位时序寄存器 CAN_BTR 中的 BRP[9:0] 寄存器位可以设置

CAN波特率=Fpclk1/((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)

4.3 功能说明

4.3.1 发送过程

基本流程:选择一个空置邮箱->写入报文->请求发送

4.3.2 接收过程

基本流程:接收到一个报文->匹配过滤器后进入FIFO 0或FIFO1 ->CPU读取

4.3.3 标识符过滤器

  • 每个过滤器的核心由两个32位寄存器组成R1[31:0]和R2[31:0]

  • FSCx:位宽设置 置0:16位;置1:32位

  • FBMx:模式设置 置0:屏蔽模式;置1:列表模式

  • FFAx:关联设置 置0:FIFO 0;置1:FIFO 1

  • FACTx:激活设置 置0:禁用;置1:启用

  • 想要接收的ID可以用16位表示并且没有什么模糊规律所以采用16位/列表模式。数据帧RTR为0,IDE无扩展为0,EXID无扩展ID所以为000;

    真实数据位左对齐,所以向左平移5位。

  • 想要接收的ID可以用16位表示并且没有什么模糊规律所以采用16位/列表模式。数据帧RTR为0,IDE无扩展为0,EXID无扩展ID所以为000;

    真实数据位左对齐,所以向左平移5位

  • 0x123在STID够用,IDE为0,RTR为0,0,所以左移21位

    0x12345678不够用,所以还需要扩展,IDE为1,RTR为0,0,所以左移3位,并或上0x

4.3.4 中断

  • CAN外设占用4个专用的中断向量

  • 发送中断:发送邮箱空时产生

  • FIFO 0中断:收到一个报文/FIFO 0满/FIFO 0溢出时产生

  • FIFO 1中断:收到一个报文/FIFO 1满/FIFO 1溢出时产生

  • 状态改变错误中断:出错/唤醒/进入睡眠时产生

时间触发通信

五、库函数介绍

5.1 初始化

typedef struct
{
  uint16_t CAN_Prescaler;   /* 配置 CAN 外设的时钟分频,可设置为 1-1024*/
  uint8_t CAN_Mode;         /* 配置 CAN 的工作模式,回环或正常模式 */
  uint8_t CAN_SJW;          /* 配置 SJW 极限值 */
  uint8_t CAN_BS1;          /* 配置 BS1 段长度 */
  uint8_t CAN_BS2;          /* 配置 BS2 段长度 */
  FunctionalState CAN_TTCM; /* 是否使能 TTCM 时间触发功能 */
  FunctionalState CAN_ABOM; /* 是否使能 ABOM 自动离线管理功能 */
  FunctionalState CAN_AWUM; /* 是否使能 AWUM 自动唤醒功能 */
  FunctionalState CAN_NART; /* 是否使能 NART 自动重传功能 */
  FunctionalState CAN_RFLM; /* 是否使能 RFLM 锁定 FIFO 功能 */
  FunctionalState CAN_TXFP; /* 配置 TXFP 报文优先级的判定方法 */
} CAN_InitTypeDef;

(1) Prescaler

本成员设置 CAN 外设的时钟分频,它可控制时间片 Tq 的时间长度,这里设置的值最终会减 1 后再写入 BRP 寄存器位,即前面介绍的 Tq 计算公式:Tq = (BRP[9:0]+1) x TPCLK

等效于:Tq = CAN_Prescaler x TPCLK

(2) Mode

本成员设置 CAN 的工作模式,可设置为正常模式 (CAN_MODE_NORMAL)、回环模式 (CAN_MODE_LOOPBACK)、静默模式 (CAN_MODE_SILENT) 以及回环静默模式(CAN_MODE_SILENT_LOOPBACK)。

(3) SyncJumpWidth

本成员可以配置 SJW 的极限长度,即 CAN 重新同步时单次可增加或缩短的最大长度,它可以被配置为 1-4Tq(CAN_SJW_1/2/3/4tq)。

(4) TimeSeg1

本成员用于设置 CAN 位时序中的 BS1 段的长度,它可以被配置为 1-16 个 Tq 长度(CAN_BS1_1/2/3…16tq)。

(5) TimeSeg2

本成员用于设置 CAN 位时序中的 BS2 段的长度,它可以被配置为 1-8 个 Tq 长度(CAN_BS2_1/2/3…8tq)。SYNC_SEG、 BS1 段及 BS2 段的长度加起来即一个数据位的长度,即前面介绍的原来

计算公式:T1bit =1Tq+TS1+TS2=1+ (TS1[3:0] + 1)+ (TS2[2:0] + 1)

等效于:T1bit= 1Tq+CAN_BS1+CAN_BS2

(6) TimeTriggeredMode

本成员用于设置是否使用时间触发功能 (ENABLE/DISABLE),时间触发功能在某些CAN 标准中会使用到。

(7) AutoBusOff

本成员用于设置是否使用自动离线管理 (ENABLE/DISABLE),使用自动离线管理可以在节点出错离线后适时自动恢复,不需要软件干预。

(8) AutoWakeUp

本成员用于设置是否使用自动唤醒功能 (ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒。

(9) AutoWakeUp

本成员用于设置是否使用自动离线管理功能 (ENABLE/DISABLE),使用自动离线管理可以在出错时离线后适时自动恢复,不需要软件干预。

(10) AutoRetransmission

本成员用于设置是否使用自动重传功能 (ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止。

(11) ReceiveFifoLocked

本成员用于设置是否使用锁定接收 FIFO(ENABLE/DISABLE),锁定接收 FIFO 后,若FIFO 溢出时会丢弃新数据,否则在 FIFO 溢出时以新数据覆盖旧数据。

(12) TransmitFifoPriority

本成员用于设置发送报文的优先级判定方法 (ENABLE/DISABLE),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文 ID 的优先级来发送。

5.2 CAN筛选器结构体

typedef struct
{
  uint16_t CAN_FilterIdHigh;        /*CAN_FxR1 寄存器的高 16 位 */
  uint16_t CAN_FilterIdLow;         /*CAN_FxR1 寄存器的低 16 位 */
  uint16_t CAN_FilterMaskIdHigh;    /*CAN_FxR2 寄存器的高 16 位 */
  uint16_t CAN_FilterMaskIdLow;     /*CAN_FxR2 寄存器的低 16 位 */
  uint16_t CAN_FilterFIFOAssignment;/* 设置经过筛选后数据存储到哪个接收 FIFO */
  uint8_t CAN_FilterNumber;         /* 筛选器编号,范围 0-27,数据手册上说0-27是CAN1/CAN2共享,但是实测发现并不是这样,CAN1是0-13,CAN2是14-27 */
  uint8_t CAN_FilterMode;           /* 筛选器模式 */
  uint8_t CAN_FilterScale;          /* 设置筛选器的尺度 */
  FunctionalState CAN_FilterActivation; /* 是否使能本筛选器 */
} CAN_FilterInitTypeDef;

(1) FilterIdHigh

FilterIdHigh 成员用于存储要筛选的 ID,若筛选器工作在 32 位模式,它存储的是所筛选 ID 的高 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID。

(2) FilterIdLow

类似地,FilterIdLow 成员也是用于存储要筛选的 ID,若筛选器工作在 32 位模式,它存储的是所筛选 ID 的低 16 位;若筛选器工作在 16 位模式,它存储的就是一个完整的要筛选的 ID。

(3) FilterMaskIdHigh

FilterMaskIdHigh 存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与 FilterIdHigh 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 FilterIdHigh 成员对应的掩码,与 FilterIdLow 组成一组筛选器。

(4) FilterMaskIdLow

类似地, FilterMaskIdLow 存储的内容也分两种情况,当筛选器工作在标识符列表模式时,它的功能与 FilterIdLow 相同,都是存储要筛选的 ID;而当筛选器工作在掩码模式时,它存储的是 FilterIdLow 成员对应的掩码,与 FilterIdLow 组成一组筛选器。上面四个结构体的存储的内容很容易让人糊涂,请结合前面的图 39_0_15 和下面的表 39‑7 理解,如果还搞不清楚,再结合库函数 FilterInit 的源码来分析。

表不同模式下各结构体成员的内容

对这些结构体成员赋值的时候,还要注意寄存器位的映射,即注意哪部分代表 STID,哪部分代表 EXID 以及 IDE、RTR 位。

(5) FilterFIFOAssignment

本成员用于设置当报文通过筛选器的匹配后,该报文会被存储到哪一个接收 FIFO,它的可选值为 FIFO0 或 FIFO1(宏 CAN_FILTER_FIFO0/1)。

(6) FilterBank

本成员用于设置筛选器的编号,即本过滤器结构体配置的是哪一组筛选器,CAN 一共有 28 个筛选器,所以它的可输入参数范围为 0-27。

(7) FilterMode

本 成 员 用 于 设 置 筛 选 器 的 工 作 模 式, 可 以 设 置 为 列 表 模 式 (宏CAN_FILTERMODE_IDLIST) 及掩码模式 (宏 CAN_FILTERMODE_IDMASK)。

(8) FilterScale

本成员用于设置筛选器的尺度,可以设置为 32 位长 (宏 CAN_FILTERSCALE_32BIT)及 16 位长 (宏 CAN_FILTERSCALE_16BIT)。

(9) FilterActivation

本成员用于设置是否激活这个筛选器 (宏 ENABLE/DISABLE)。

5.3 CAN发送及接收结构体

typedef struct
{
  uint32_t StdId; /* 存储报文的标准标识符 11 位,0-0x7FF. */
  uint32_t ExtId; /* 存储报文的扩展标识符 29 位,0-0x1FFFFFFF. */
  uint8_t IDE;    /* 存储 IDE 扩展标志 */
  uint8_t RTR;    /* 存储 RTR 远程帧标志 */
  uint8_t DLC;    /* 存储报文数据段的长度,0-8 */
  uint8_t Data[8]; 
} CanTxMsg;
typedef struct
{
  uint32_t StdId; /* 存储报文的标准标识符 11 位,0-0x7FF. */
  uint32_t ExtId; /* 存储报文的扩展标识符 29 位,0-0x1FFFFFFF. */
  uint8_t IDE;    /* 存储 IDE 扩展标志 */
  uint8_t RTR;    /* 存储 RTR 远程帧标志 */
  uint8_t DLC;    /* 存储报文数据段的长度,0-8 */   
  uint8_t Data[8]; 
  uint8_t FMI;     
} CanRxMsg;

(1) StdId

本成员存储的是报文的 11 位标准标识符,范围是 0-0x7FF。

(2) ExtId

本成员存储的是报文的 29 位扩展标识符,范围是 0-0x1FFFFFFF。ExtId 与 StdId 这两个成员根据下面的 IDE 位配置,只有一个是有效的。

(3) IDE

本成员存储的是扩展标志 IDE 位,当它的值为宏 CAN_ID_STD 时表示本报文是标准帧,使用 StdId 成员存储报文 ID;当它的值为宏 CAN_ID_EXT 时表示本报文是扩展帧,使用 ExtId 成员存储报文 ID。

(4) RTR

本成员存储的是报文类型标志 RTR 位,当它的值为宏 CAN_RTR_Data 时表示本报文是数据帧;当它的值为宏 CAN_RTR_Remote 时表示本报文是遥控帧,由于遥控帧没有数据段,所以当报文是遥控帧时,数据是无效的

(5) DLC

本成员存储的是数据帧数据段的长度,它的值的范围是 0-8,当报文是遥控帧时 DLC值为 0。

六、实验

6.1 单个设备环回测试

串口配置
void My_UsartInit(void)
{
    //第一步:开usart1、和 GPIOA 的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //USART1
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);  //GPIOA
    
    //第二步:配置GPIOA
    GPIO_InitTypeDef Struct;
    Struct.GPIO_Mode = GPIO_Mode_AF; //配置为复用模式(把gpio_pin9 配置为复用)
    Struct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
    Struct.GPIO_Speed = GPIO_High_Speed;
    GPIO_Init(GPIOA,&Struct);
    
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //把gpio_pin9复用到usart1上
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);//把gpio_pin10复用到usart1上
    
    //第三步:配置USART
    USART_InitTypeDef Struct1;
    Struct1.USART_BaudRate = 9600;  //设置波特率
    Struct1.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控
    Struct1.USART_Mode = USART_Mode_Rx |USART_Mode_Tx; //使能发送和接收引脚
    Struct1.USART_Parity = USART_Parity_No;  //无校验位
    Struct1.USART_StopBits = USART_StopBits_1;  //一位停止位
    Struct1.USART_WordLength = USART_WordLength_8b;  //8位数据位  
    USART_Init(USART1,&Struct1);
    
    USART_Cmd(USART1,ENABLE); //使能 usart1,让它开始工作
}
​
int fputc(int ch, FILE *f)
{
    USART_SendData(USART1,ch);
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    return ch;
}
按键
void Key_Config()
{
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); // 使能GPIOE时钟
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; // 输入模式
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // 推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; // PE2
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN; // 下拉
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; // 速度100MHz
    GPIO_Init(GPIOE, &GPIO_InitStruct);
}
Led
void GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); // 使能GPIOF时钟
    
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; // 输出模式
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // 推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; // PF9
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上拉下拉
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; // 速度100MHz
    GPIO_Init(GPIOF, &GPIO_InitStruct);
    
    GPIO_SetBits(GPIOF, GPIO_Pin_9); // 初始状态为关闭
    GPIO_SetBits(GPIOF, GPIO_Pin_10);
}
void Led1_ON()
{
    GPIO_ResetBits(GPIOF,GPIO_Pin_9);
}
void Led1_Off()
{
    GPIO_SetBits(GPIOF,GPIO_Pin_9);
}
void Led2_ON()
{
    GPIO_ResetBits(GPIOF,GPIO_Pin_10);
}
void Led2_Off()
{
    GPIO_SetBits(GPIOF,GPIO_Pin_10);
}
CAN
void My_CAN_Init(void)
{
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
    GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12;
    GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_DOWN;
    GPIO_InitStruct.GPIO_Speed=GPIO_High_Speed;
    GPIO_Init(GPIOA,&GPIO_InitStruct);
    
    GPIO_InitTypeDef GPIO_InitStruct1;
    GPIO_InitStruct1.GPIO_Mode=GPIO_Mode_IN;
    GPIO_InitStruct1.GPIO_OType=GPIO_OType_PP;
    GPIO_InitStruct1.GPIO_Pin=GPIO_Pin_11;
    GPIO_InitStruct1.GPIO_PuPd=GPIO_PuPd_UP;
    GPIO_InitStruct1.GPIO_Speed=GPIO_High_Speed;
    GPIO_Init(GPIOA,&GPIO_InitStruct1);
    
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);
    
    CAN_InitTypeDef CAN_InitStruct;
    CAN_InitStruct.CAN_Mode=CAN_Mode_LoopBack;
    CAN_InitStruct.CAN_Prescaler=28;
    CAN_InitStruct.CAN_BS1=CAN_BS1_8tq;
    CAN_InitStruct.CAN_BS2=CAN_BS2_3tq;
    CAN_InitStruct.CAN_SJW=CAN_SJW_1tq; //42M/48/(1+4+3)
    CAN_InitStruct.CAN_NART=DISABLE;
    CAN_InitStruct.CAN_TXFP=DISABLE;
    CAN_InitStruct.CAN_RFLM=DISABLE;
    CAN_InitStruct.CAN_AWUM=DISABLE;
    CAN_InitStruct.CAN_TTCM=DISABLE;
    CAN_InitStruct.CAN_ABOM=DISABLE;
    CAN_Init(CAN1,&CAN_InitStruct);
    
    CAN_FilterInitTypeDef CAN_FilterInitStruct;
    CAN_FilterInitStruct.CAN_FilterNumber=0;
    CAN_FilterInitStruct.CAN_FilterIdHigh=0x0000;
    CAN_FilterInitStruct.CAN_FilterIdLow=0x0000;
    CAN_FilterInitStruct.CAN_FilterMaskIdHigh=0x0000;
    CAN_FilterInitStruct.CAN_FilterMaskIdLow=0x0000;
    CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_32bit;
    CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdMask;
    CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
    CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
    CAN_FilterInit(&CAN_FilterInitStruct);
    
}
​
uint8_t MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t* Data) {
    CanTxMsg TxMessage;
    for (uint8_t i = 0; i < Length; i++) {
        TxMessage.Data[i] = Data[i];
    }
    TxMessage.DLC = Length;
    TxMessage.ExtId = ID;
    TxMessage.IDE = CAN_Id_Standard;
    TxMessage.RTR = CAN_RTR_Data;
    TxMessage.StdId = ID;
    uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);
​
    uint32_t Timeout = 0;
    while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok) {
        if (Timeout > 100000) {
            return 0; // 发送失败
        }
        Timeout++;
    }
    return 1; // 发送成功
}
​
uint8_t MyCAN_ReceiveFlag(void)
{
    if(CAN_MessagePending(CAN1,CAN_FIFO0)> 0)
    {
        return 1;
    }
    return 0;
}
​
void MyCAN_Receive(uint32_t *ID,uint8_t *Length,uint8_t*Data)
{
     CanRxMsg RxMessage;
    CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);
    if(RxMessage.IDE==CAN_Id_Standard)
    {
        *ID=RxMessage.StdId;
    }else{
        *ID=RxMessage.ExtId;
    }
    if(RxMessage.RTR==CAN_RTR_Data)
    {
        *Length=RxMessage.DLC;
        for(uint8_t i=0;i<*Length;i++)
        {
            Data[i]=RxMessage.Data[i];
        }
    }else{
        //....
    }
}
主函数配置

6.2 三个设备互相通信

只需要在单设备环回测试的基础上修改CAN的模式;

CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.CAN_Mode=CAN_Mode_Normal;
CAN_InitStruct.CAN_Prescaler=28;
CAN_InitStruct.CAN_BS1=CAN_BS1_8tq;
CAN_InitStruct.CAN_BS2=CAN_BS2_3tq;
CAN_InitStruct.CAN_SJW=CAN_SJW_1tq; //42M/48/(1+4+3)
CAN_InitStruct.CAN_NART=DISABLE;
CAN_InitStruct.CAN_TXFP=DISABLE;
CAN_InitStruct.CAN_RFLM=DISABLE;
CAN_InitStruct.CAN_AWUM=DISABLE;
CAN_InitStruct.CAN_TTCM=DISABLE;
CAN_InitStruct.CAN_ABOM=DISABLE;
CAN_Init(CAN1,&CAN_InitStruct);

6.3 标准格式-扩展格式-数据帧-遥控帧

#include "stm32f4xx.h"
void My_CAN_Init(void)
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF;
	GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_DOWN;
	GPIO_InitStruct.GPIO_Speed=GPIO_High_Speed;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitTypeDef GPIO_InitStruct1;
	GPIO_InitStruct1.GPIO_Mode=GPIO_Mode_IN;
	GPIO_InitStruct1.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStruct1.GPIO_Pin=GPIO_Pin_11;
	GPIO_InitStruct1.GPIO_PuPd=GPIO_PuPd_UP;
	GPIO_InitStruct1.GPIO_Speed=GPIO_High_Speed;
	GPIO_Init(GPIOA,&GPIO_InitStruct1);
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);
	
	CAN_InitTypeDef CAN_InitStruct;
	CAN_InitStruct.CAN_Mode=CAN_Mode_LoopBack;//CAN_Mode_Normal
	CAN_InitStruct.CAN_Prescaler=28;
	CAN_InitStruct.CAN_BS1=CAN_BS1_8tq;
	CAN_InitStruct.CAN_BS2=CAN_BS2_3tq;
	CAN_InitStruct.CAN_SJW=CAN_SJW_1tq; //42M/48/(1+4+3)
	CAN_InitStruct.CAN_NART=DISABLE;
	CAN_InitStruct.CAN_TXFP=DISABLE;
	CAN_InitStruct.CAN_RFLM=DISABLE;
	CAN_InitStruct.CAN_AWUM=DISABLE;
	CAN_InitStruct.CAN_TTCM=DISABLE;
	CAN_InitStruct.CAN_ABOM=DISABLE;
	CAN_Init(CAN1,&CAN_InitStruct);
	
	CAN_FilterInitTypeDef CAN_FilterInitStruct;
	CAN_FilterInitStruct.CAN_FilterNumber=0;
	CAN_FilterInitStruct.CAN_FilterIdHigh=0x0000;
	CAN_FilterInitStruct.CAN_FilterIdLow=0x0000;
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh=0x0000;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow=0x0000;
	CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_32bit;
	CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdMask;
	
	CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
	CAN_FilterInit(&CAN_FilterInitStruct);
	
}

uint8_t MyCAN_Transmit(CanTxMsg *TxMessage) {
    
   
    uint8_t TransmitMailbox = CAN_Transmit(CAN1, TxMessage);

    uint32_t Timeout = 0;
    while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok) {
        if (Timeout > 100000) {
            return 0; // 发送失败
        }
        Timeout++;
    }
    return 1; // 发送成功
}

uint8_t MyCAN_ReceiveFlag(void)
{
	if(CAN_MessagePending(CAN1,CAN_FIFO0)> 0)
	{
		return 1;
	}
	return 0;
}

void MyCAN_Receive(CanRxMsg *RxMessage)
{
	 
	CAN_Receive(CAN1,CAN_FIFO0,RxMessage);
	
}
 CanTxMsg TxMsgArray[] = {
	 /*StdId      ExtId      IDE            RTR         DLC    Data[8]*/
	 {0x555,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x555,0x12345678,CAN_Id_Extended,CAN_RTR_Data,4,{0xAA,0xBB,0xCC,0xDD}},
	 {0x555,0x00000000,CAN_Id_Standard,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
	 {0x555,0x14785236,CAN_Id_Extended,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
    };
 uint8_t pTxMsgArray=0;
CanRxMsg RxMsg;
int main()
{
	My_UsartInit();
	Key_Config();
	printf("Ok\n");
	My_CAN_Init();
	GPIO_Config();
	printf("Ok\n");
	//uint8_t TxData[]={66,88};

	while(1){
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
		{
			delay_ms(100);
			while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
			{
				 
         MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
				 pTxMsgArray++;
				if(pTxMsgArray>=sizeof(TxMsgArray)/sizeof(CanTxMsg))
				{
					pTxMsgArray=0;
				}
				 
			}
		}
		
		delay_ms(10);
		if(MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			if(RxMsg.IDE==CAN_Id_Standard)
			{
				printf("Standard\n");
				//是标准格式
				printf("RxMsg.StdId=%x\n",RxMsg.StdId);
			}
			else if(RxMsg.IDE==CAN_Id_Extended)
			{
				//是扩展格式
				printf("Extended\n");
				printf("RxMsg.ExtId=%x\n",RxMsg.ExtId);
			}
			if(RxMsg.RTR==CAN_RTR_Data)
			{
				printf("Data\n");
				printf("RxMsg.DLC=%d\n",RxMsg.DLC);
				for(int i=0;i<RxMsg.DLC;i++){
					printf("%x ",RxMsg.Data[i]);
				}
				printf("\n");
			}
			else if(RxMsg.RTR==CAN_RTR_Remote)
			{
				printf("Remote\n");
				printf("RxMsg.DLC=%d\n",RxMsg.DLC);
			}
			
		}
	}
}

6.4 标识符过滤器-16位列表

6.4.1 配置筛选器
    CAN_FilterInitTypeDef CAN_FilterInitStruct;
	CAN_FilterInitStruct.CAN_FilterNumber=0;
	CAN_FilterInitStruct.CAN_FilterIdHigh=0x234<<5;
	CAN_FilterInitStruct.CAN_FilterIdLow=0x345<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh=0x567<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow=0x0000;
	CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_16bit;
	CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdList;
	
	CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
	CAN_FilterInit(&CAN_FilterInitStruct);
6.4.2主函数实现
CanTxMsg TxMsgArray[] = {
	 /*StdId      ExtId      IDE            RTR         DLC    Data[8]*/
	 {0x123,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x234,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x345,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x456,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x567,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x678,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 //{0x555,0x12345678,CAN_Id_Extended,CAN_RTR_Data,4,{0xAA,0xBB,0xCC,0xDD}},
	 //{0x555,0x00000000,CAN_Id_Standard,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
	 //{0x555,0x14785236,CAN_Id_Extended,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
    };
 uint8_t pTxMsgArray=0;
CanRxMsg RxMsg;
int main()
{
	My_UsartInit();
	Key_Config();
	printf("Ok\n");
	My_CAN_Init();
	GPIO_Config();
	printf("Ok\n");
	//uint8_t TxData[]={66,88};

	while(1){
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
		{
			delay_ms(100);
			while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
			{
				 
         MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
				printf("pTxMsgArray=%d:\n",pTxMsgArray);
				printf("StdId=%x\n",TxMsgArray[pTxMsgArray].StdId);
				printf("StdId=%x\n",TxMsgArray[pTxMsgArray].ExtId);
				printf("StdId=%d\n",TxMsgArray[pTxMsgArray].IDE);
				printf("StdId=%d\n",TxMsgArray[pTxMsgArray].RTR);
				printf("StdId=%d\n",TxMsgArray[pTxMsgArray].DLC);
				for(int i=0;i<TxMsgArray[pTxMsgArray].DLC;i++)
				{
					printf("%x ",TxMsgArray[pTxMsgArray].Data[i]);
				}
				printf("\n");
				 pTxMsgArray++;
				if(pTxMsgArray>=sizeof(TxMsgArray)/sizeof(CanTxMsg))
				{
					pTxMsgArray=0;
				}
				 
			}
		}
		
		delay_ms(10);
		if(MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			if(RxMsg.IDE==CAN_Id_Standard)
			{
				printf("Standard\n");
				//是标准格式
				printf("RxMsg.StdId=%x\n",RxMsg.StdId);
			}
			else if(RxMsg.IDE==CAN_Id_Extended)
			{
				//是扩展格式
				printf("Extended\n");
				printf("RxMsg.ExtId=%x\n",RxMsg.ExtId);
			}
			if(RxMsg.RTR==CAN_RTR_Data)
			{
				printf("Data\n");
				printf("RxMsg.DLC=%d\n",RxMsg.DLC);
				for(int i=0;i<RxMsg.DLC;i++){
					printf("%x ",RxMsg.Data[i]);
				}
				printf("\n");
			}
			else if(RxMsg.RTR==CAN_RTR_Remote)
			{
				printf("Remote\n");
				printf("RxMsg.DLC=%d\n",RxMsg.DLC);
			}
			
		}
	}
}
6.4.3 实验结果

由此图可知,发送出0x123,0x234,0x345,0x456,0x567,0x678只接收0x234,0x345,0x567

6.4.4 多个过滤器共同工作
CAN_FilterInitTypeDef CAN_FilterInitStruct;
	CAN_FilterInitStruct.CAN_FilterNumber=0;
	CAN_FilterInitStruct.CAN_FilterIdHigh=0x234<<5;
	CAN_FilterInitStruct.CAN_FilterIdLow=0x345<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh=0x567<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow=0x0000;
	CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_16bit;
	CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdList;
	
	CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
	CAN_FilterInit(&CAN_FilterInitStruct);
	
	CAN_FilterInitStruct.CAN_FilterNumber=1;
	CAN_FilterInitStruct.CAN_FilterIdHigh=0x123<<5;
	CAN_FilterInitStruct.CAN_FilterIdLow=0x678<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh=0x000<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow=0x0000;
	CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_16bit;
	CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdList;
	
	CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
	CAN_FilterInit(&CAN_FilterInitStruct);

6.5 标识符过滤器-16屏蔽

6.5.1 配置筛选器
CAN_FilterInitTypeDef CAN_FilterInitStruct;
	CAN_FilterInitStruct.CAN_FilterNumber=0;
	CAN_FilterInitStruct.CAN_FilterIdHigh=0x200<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh=(0x700<<5)|0x10|0x8;
		
	CAN_FilterInitStruct.CAN_FilterIdLow=0x320<<5;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow=(0x7F0<<5)|0x10|0x8;
	
	CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_16bit;
	CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdMask;
	
	CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
	CAN_FilterInit(&CAN_FilterInitStruct);
6.4.2 主函数实现
CanTxMsg TxMsgArray[] = {
	 /*StdId      ExtId      IDE            RTR         DLC    Data[8]*/
	 {0x100,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x101,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x1FE,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x1FF,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 
	 {0x200,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x201,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x2FE,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x2FF,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 
	 {0x310,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x311,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x31E,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x31F,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 
	 {0x320,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x321,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x32E,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x32F,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 //{0x555,0x12345678,CAN_Id_Extended,CAN_RTR_Data,4,{0xAA,0xBB,0xCC,0xDD}},
	 //{0x555,0x00000000,CAN_Id_Standard,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
	 //{0x555,0x14785236,CAN_Id_Extended,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
    };
 uint8_t pTxMsgArray=0;
CanRxMsg RxMsg;
int main()
{
	My_UsartInit();
	Key_Config();
	printf("Ok\n");
	My_CAN_Init();
	GPIO_Config();
	printf("Ok\n");
	//uint8_t TxData[]={66,88};

	while(1){
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
		{
			delay_ms(100);
			while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
			{
				 
         MyCAN_Transmit(&TxMsgArray[pTxMsgArray]);
				printf("pTxMsgArray=%d:\n",pTxMsgArray);
				printf("StdId=%x\n",TxMsgArray[pTxMsgArray].StdId);
				printf("StdId=%x\n",TxMsgArray[pTxMsgArray].ExtId);
				printf("StdId=%d\n",TxMsgArray[pTxMsgArray].IDE);
				printf("StdId=%d\n",TxMsgArray[pTxMsgArray].RTR);
				printf("StdId=%d\n",TxMsgArray[pTxMsgArray].DLC);
				for(int i=0;i<TxMsgArray[pTxMsgArray].DLC;i++)
				{
					printf("%x ",TxMsgArray[pTxMsgArray].Data[i]);
				}
				printf("\n");
				 pTxMsgArray++;
				if(pTxMsgArray>=sizeof(TxMsgArray)/sizeof(CanTxMsg))
				{
					pTxMsgArray=0;
				}
				 
			}
		}
		
		delay_ms(10);
		if(MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			if(RxMsg.IDE==CAN_Id_Standard)
			{
				printf("Standard\n");
				//是标准格式
				printf("RxMsg.StdId=%x\n",RxMsg.StdId);
			}
			else if(RxMsg.IDE==CAN_Id_Extended)
			{
				//是扩展格式
				printf("Extended\n");
				printf("RxMsg.ExtId=%x\n",RxMsg.ExtId);
			}
			if(RxMsg.RTR==CAN_RTR_Data)
			{
				printf("Data\n");
				printf("RxMsg.DLC=%d\n",RxMsg.DLC);
				for(int i=0;i<RxMsg.DLC;i++){
					printf("%x ",RxMsg.Data[i]);
				}
				printf("\n");
			}
			else if(RxMsg.RTR==CAN_RTR_Remote)
			{
				printf("Remote\n");
				printf("RxMsg.DLC=%d\n",RxMsg.DLC);
			}
			
		}
	}
}

6.4.3 实现结果

对于0x200-0x2ff,发送成功,经过过滤器成功接收。

同理0x320-0x32f,发送成功,经过过滤器成功接收;而0x310-0x31f发送成功,但被过滤器过滤了

6.6 标识符过滤器-32位列表

CAN_FilterInitTypeDef CAN_FilterInitStruct;
	CAN_FilterInitStruct.CAN_FilterNumber=0;
	uint32_t ID1=0x123<<21;
	uint32_t ID2=(0x12345678u<<3)|0x4;
	CAN_FilterInitStruct.CAN_FilterIdHigh=ID1>>16;
	CAN_FilterInitStruct.CAN_FilterIdLow=ID1;
	
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh=ID2>>16;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow=ID2;
	
	CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_32bit;
	CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdList;
	
	CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
	CAN_FilterInit(&CAN_FilterInitStruct);

6.7 标识符过滤器-32位屏蔽

CAN_FilterInitTypeDef CAN_FilterInitStruct;
	CAN_FilterInitStruct.CAN_FilterNumber=0;
	uint32_t ID1=(0x12345600u)<<3|0x4;
	uint32_t ID2=(0x1FFFFFF0u<<3)|0x4|0x2;
	CAN_FilterInitStruct.CAN_FilterIdHigh=ID1>>16;
	CAN_FilterInitStruct.CAN_FilterIdLow=ID1;
	
	CAN_FilterInitStruct.CAN_FilterMaskIdHigh=ID2>>16;
	CAN_FilterInitStruct.CAN_FilterMaskIdLow=ID2;
	
	CAN_FilterInitStruct.CAN_FilterScale=CAN_FilterScale_32bit;
	CAN_FilterInitStruct.CAN_FilterMode=CAN_FilterMode_IdMask;
	
	CAN_FilterInitStruct.CAN_FilterActivation=ENABLE;
	CAN_FilterInitStruct.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
	CAN_FilterInit(&CAN_FilterInitStruct);
6.7.2 实验现象

6.8 中断式接收

6.8.1 代码实现
#include "stm32f4xx.h"
CanRxMsg MyCAN_RxMsg;
uint8_t MyCAN_RxFlag;
void My_CAN_Init(void)
{
    //增加下面代码
	CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel=CAN1_RX0_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
	NVIC_Init(&NVIC_InitStruct);
}
//删除原来的接收标志函数和接收函数
void CAN1_RX0_IRQHandler(void)
{
	if(CAN_GetITStatus(CAN1,CAN_IT_FMP0)!=RESET)
	{
		CAN_Receive(CAN1,CAN_FIFO0,&MyCAN_RxMsg);
		MyCAN_RxFlag=1;
		CAN_ClearITPendingBit(CAN1,CAN_IT_FMP0);
	}
}


#include "stm32f4xx.h"
#include "Key.h"
#include "My_Usart.h"
#include "stdio.h"
#include "Led.h"
#include "m_delay.h"
#include "CAN.h"
#define KEY_ON  1
#define KEY_OFF 0

extern CanRxMsg MyCAN_RxMsg;
extern uint8_t MyCAN_RxFlag;
 CanTxMsg TxMsgArray[] = {
	 /*StdId      ExtId      IDE            RTR         DLC    Data[8]*/
	 {0x555,0x00000000,CAN_Id_Standard,CAN_RTR_Data,4,{0x11, 0x22, 0x33, 0x44}},
	 {0x555,0x12345678,CAN_Id_Extended,CAN_RTR_Data,4,{0xAA,0xBB,0xCC,0xDD}},
	 {0x555,0x00000000,CAN_Id_Standard,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
	 {0x555,0x14785236,CAN_Id_Extended,CAN_RTR_Remote,0,{0x00, 0x00, 0x00, 0x00}},
    };
 uint8_t pTxMsgArray=0;
int main()
{
	My_UsartInit();
	Key_Config();
	printf("Ok\n");
	My_CAN_Init();
	printf("Ok\n");
	while(1){
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF) {
			delay_ms(100);
			while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF) {
				if(MyCAN_Transmit(&TxMsgArray[pTxMsgArray]) == 0) {
					printf("CAN transmit failed.\n");
				} else {
					printf("pTxMsgArray=%d:\n", pTxMsgArray);
					printf("StdId=%x\n", TxMsgArray[pTxMsgArray].StdId);
					if(TxMsgArray[pTxMsgArray].ExtId != 0) {
						printf("ExtId=%x\n", TxMsgArray[pTxMsgArray].ExtId);
					}
					printf("\n");
				}
				pTxMsgArray++;
				if(pTxMsgArray >= sizeof(TxMsgArray)/sizeof(CanTxMsg)) {
					pTxMsgArray = 0;
				}
			}
		}
		delay_ms(10);
		if(MyCAN_RxFlag==1)
		{
			MyCAN_RxFlag=0;
			if(MyCAN_RxMsg.IDE==CAN_Id_Standard)
			{
				printf("Standard\n");
				//是标准格式
				printf("RxMsg.StdId=%x\n",MyCAN_RxMsg.StdId);
			}
			else if(MyCAN_RxMsg.IDE==CAN_Id_Extended)
			{
				//是扩展格式
				printf("Extended\n");
				printf("RxMsg.ExtId=%x\n",MyCAN_RxMsg.ExtId);
			}
			if(MyCAN_RxMsg.RTR==CAN_RTR_Data)
			{
				printf("Data\n");
				printf("RxMsg.DLC=%d\n",MyCAN_RxMsg.DLC);
				for(int i=0;i<MyCAN_RxMsg.DLC;i++){
					printf("%x ",MyCAN_RxMsg.Data[i]);
				}
				printf("\n");
			}
			else if(MyCAN_RxMsg.RTR==CAN_RTR_Remote)
			{
				printf("Remote\n");
				printf("RxMsg.DLC=%d\n",MyCAN_RxMsg.DLC);
			}
			
		}
	}
}
6.8.2 实验结果

6.9 数据传输策略

6.9.1 定时传输
CanTxMsg TxMsg_Timing ={
	.StdId=0x100,
	.ExtId=0x00000000,
	.IDE=CAN_Id_Standard,
	.RTR=CAN_RTR_Data,
	.DLC=4,
	.Data={0x11,0x22,0x33,0x44}
};
int main()
{
	while(1)
	{
		TxMsg_Timing.Data[0]++;
		TxMsg_Timing.Data[1]++;
		TxMsg_Timing.Data[2]++;
		TxMsg_Timing.Data[3]++;
		MyCAN_Transmit(&TxMsg_Timing);
		printf("%x\n",TxMsg_Timing.Data[0]);
		printf("%x\n",TxMsg_Timing.Data[1]);
		printf("%x\n",TxMsg_Timing.Data[2]);
		printf("%x\n",TxMsg_Timing.Data[3]);		
		delay_ms(1000);
	}
}
CanRxMsg RxMsg;
int main()
{
	while(1)
	{
		if(MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			if(RxMsg.RTR==CAN_RTR_Data)
			{
				if(RxMsg.StdId==0x100&&RxMsg.IDE==CAN_Id_Standard)
				{
					printf("%x\n",RxMsg.Data[0]);
					printf("%x\n",RxMsg.Data[1]);
					printf("%x\n",RxMsg.Data[2]);
					printf("%x\n",RxMsg.Data[3]);
				}
			}
		}
		
	}
}
6.9.2 发送方触发发送
CanTxMsg TxMsg_Trigger ={
	.StdId=0x200,
	.ExtId=0x00000000,
	.IDE=CAN_Id_Standard,
	.RTR=CAN_RTR_Data,
	.DLC=4,
	.Data={0x11,0x22,0x33,0x44}
};
int main()
{
	while(1)
	{
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
		{
			delay_ms(100);
			while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)==KEY_OFF)
			{
				 Trigger_Flag=1;
			}
		}
		if(Trigger_Flag==1){
			Trigger_Flag=0;
			TxMsg_Trigger.Data[0]++;
			TxMsg_Trigger.Data[1]++;
			TxMsg_Trigger.Data[2]++;
			TxMsg_Trigger.Data[3]++;
			MyCAN_Transmit(&TTxMsg_Trigger);
			printf("%x\n",TxMsg_Trigger.Data[0]);
			printf("%x\n",TxMsg_Trigger.Data[1]);
			printf("%x\n",TxMsg_Trigger.Data[2]);
			printf("%x\n",TxMsg_Trigger.Data[3]);		
		}
	}
}
CanRxMsg RxMsg;
int main()
{
	while(1)
	{
		if(MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			if(RxMsg.RTR==CAN_RTR_Data)
			{
				if(RxMsg.StdId==0x200&&RxMsg.IDE==CAN_Id_Standard)
				{
					printf("%x\n",RxMsg.Data[0]);
					printf("%x\n",RxMsg.Data[1]);
					printf("%x\n",RxMsg.Data[2]);
					printf("%x\n",RxMsg.Data[3]);
				}
			}
		}
		
	}
}

6.9.3请求传输
CanTxMsg TxMsg_Request_Remote ={
	.StdId=0x300,
	.ExtId=0x00000000,
	.IDE=CAN_Id_Standard,
	.RTR=CAN_RTR_Remote,
	.DLC=0,
	.Data={0x00}
};
int main()
{
	while(1)
	{
		if(MyCAN_ReceieveFlag())
		{
			MyCAN_Receieve(&RxMsg);
			if(RxMsg.IDE==CAN_Id_Standard&&
			   RxMsg.RTR=CAN_RTR_Remote)&&
			   RxMsg.StdId==0x300)
			{
				Request_Flag=1;
			}
		}
		if(Request_Flag==1){
			Request_Flag=0;
			TxMsg_Request_Remote.Data[0]++;
			TxMsg_Request_Remote.Data[1]++;
			TxMsg_Request_Remote.Data[2]++;
			TxMsg_Request_Remote.Data[3]++;
			MyCAN_Transmit(&TxMsg_Request_Remote);
			printf("%x\n",TxMsg_Request_Remote.Data[0]);
			printf("%x\n",TxMsg_Request_Remote.Data[1]);
			printf("%x\n",TxMsg_Request_Remote.Data[2]);
			printf("%x\n",TxMsg_Request_Remote.Data[3]);		
		}
	}
}


CanRxMsg RxMsg;
int main()
{
	while(1)
	{
		if(MyCAN_ReceiveFlag())
		{
			MyCAN_Receive(&RxMsg);
			if(RxMsg.RTR==CAN_RTR_Data)
			{
				if(RxMsg.StdId==0x300&&RxMsg.IDE==CAN_Id_Standard)
				{
					printf("%x\n",RxMsg.Data[0]);
					printf("%x\n",RxMsg.Data[1]);
					printf("%x\n",RxMsg.Data[2]);
					printf("%x\n",RxMsg.Data[3]);
				}
			}
		}
		
	}
}


 参考内容:

1.STM32F407参考手册.pdf

2.CAN入门书.pdf


http://www.kler.cn/a/450547.html

相关文章:

  • 5、mysql的读写分离
  • webserver log日志系统的实现
  • 32. 线程、进程与协程
  • Pytorch | 从零构建ParNet/Non-Deep Networks对CIFAR10进行分类
  • 【视觉SLAM:八叉树地图(Octree Map)概述】
  • Java 中 Socket 技术全面解析
  • 【人工智能设计模式:迪米特法则的智能实践】
  • 十四、从0开始卷出一个新项目之瑞萨RZN2L之栈回溯(Default_Handler/hartfault)
  • 图为科技与奥比中光强强联手,打造机器人专用的全景全域AI视觉解决方案
  • 计算机网络:IP地址相关知识总结
  • 神经网络-LeNet
  • OBIEE 12C 功能测试:运行总和的运用
  • 概率论 期末 笔记
  • JVM系列(十三) -常用调优工具介绍
  • 14-C语言多文件编程
  • CES Asia(赛逸展)有哪些科技创新产品?
  • 静态路由配置包括DHCP
  • 华为ensp--BGP路由反射器
  • 数据库操作【JDBC HIbernate Mybatis】
  • C#变量作用域详解
  • uni-app 统一请求处理 请求拦截器 响应拦截器 请求封装
  • 熊军出席ACDU·中国行南京站,详解SQL管理之道
  • 大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
  • OpenGL 笔记(2)
  • 家用无线路由器的 2.4GHz 和 5GHz
  • flink+kafka实现流数据处理学习