01 can协议概念
1.1 can 协议简介
can 是控制器局域网络 (controller area network) 的简称,它是由研发和生产汽车电子产品著称的德国 bosch 公司开发的,并最终成为国际标准(iso11519以及iso11898),是国际上应用最广泛的现场总线之一。差异点如下:
can 总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以can 为底层协议专为大型货车和重工机械车辆设计的 j1939 协议。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。
我们来贴图一个车载网络构想图
1.2 can 物理层
与 i2c、spi 等具有时钟信号的同步通讯方式不同,can 通讯并不是以时钟信号来进行同步的,它是一种异步通讯,只具有 can_high 和 can_low 两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。我们来看一个示意图
1.2.1 闭环总线网络
can 物理层的形式主要有两种,图中的 can 通讯网络是一种遵循 iso11898 标准的高速、短距离“闭环网络”,它的总线最大长度为 40m,通信速度最高为 1mbps,总线的两端各要求有一个“120 欧”的电阻。
1.2.2 开环总线网络
图中的是遵循 iso11519-2 标准的低速、远距离“开环网络”,它的最大传输距离为 1km,最高通讯速率为 125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2千欧”的电阻。
1.2.3 通讯节点
从 can 通讯网络图可了解到,can 总线上可以挂载多个通讯节点,节点之间的信号经过总线传输,实现节点间通讯。由于 can 通讯协议不对节点进行地址编码,而是对数据内容进行编码的,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。
can 通讯节点由一个 can 控制器及 can 收发器组成,控制器与收发器之间通过 can_tx 及can_rx 信号线相连,收发器与 can 总线之间使用 can_high 及 can_low 信号线相连。其中can_tx 及 can_rx 使用普通的类似 ttl 逻辑信号,而 can_high 及 can_low 是一对差分信号线,使用比较特别的差分信号,下一小节再详细说明。
当 can 节点需要发送数据时,控制器把要发送的二进制编码通过 can_tx 线发送到收发器,然后由收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线 can_high 和 can_low 线输出到 can 总线网络。而通过收发器接收总线上的数据到控制器时,则是相反的过程,收发器把总线上收到的 can_high 及 can_low 信号转化成普通的逻辑电平信号,通过 can_rx 输出到控制器中。
例如,stm32 的 can 片上外设就是通讯节点中的控制器,为了构成完整的节点,还要给它外接一个收发器,在我们实验板中使用型号为 tja1050 的芯片作为 can 收发器。can 控制器与 can收发器的关系如同 ttl 串口与 max3232 电平转换芯片的关系, max3232 芯片把 ttl 电平的串口信号转换成 rs-232 电平的串口信号,can 收发器的作用则是把 can 控制器的 ttl 电平信号转换成差分信号 (或者相反) 。
目前有以下can电平转换芯片(不全)
我们来用tja1050来看下原理图:
1.2.4 差分信号
差分信号又称差模信号,与传统使用单根信号线电压表示逻辑的方式有区别,使用差分信号传输时,需要两根信号线,这两个信号线的振幅相等,相位相反,通过两根信号线的电压差值来表示
逻辑 0 和逻辑 1。见图,它使用了 v+ 与 v-信号的差值表达出了图下方的信号。
相对于单信号线传输的方式,使用差分信号传输具有如下优点:
• 抗干扰能力强,当外界存在噪声干扰时,几乎会同时耦合到两条信号线上,而接收端只关心两个信号的差值,所以外界的共模噪声可以被完全抵消。
举一个例子,正常的单线假设逻辑1是3.3v,逻辑0假设是0v,但是如果有噪声,把3.3v弄成了0v(极端),把0v弄成了-3.3v,此时就逻辑错误,但是有can高/can低一般都作用于两根线,所以两个虽然都有噪声影响,但是差值还是不变的
• 能有效抑制它对外部的电磁干扰,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。
举一个例子,假设一根是10v,一根是-10v,单跟都会对外部造成电磁干扰,但是can可以把线拧在一起,跟编麻花一样,可以互相抵消电子干扰
• 时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。
由于差分信号线具有这些优点,所以在 usb 协议、485 协议、以太网协议及 can 协议的物理层中,都使用了差分信号传输。
1.2.5 can 协议中的差分信号
can 协议中对它使用的 can_high 及 can_low 表示的差分信号做了规定,见表及图。以高速 can 协议为例,当表示逻辑 1 时 (隐性电平) ,can_high 和 can_low 线上的电压均为 2.5v,即它们的电压差 vh-vl=0v;而表示逻辑 0 时 (显性电平) ,can_high 的电平为 3.5v,can_low 线的电平为 1.5v,即它们的电压差为 vh-vl=2v。例如,当 can收发器从 can_tx 线接收到来自 can 控制器的低电平信号时 (逻辑 0),它会使 can_high 输出3.5v,同时 can_low 输出 1.5v,从而输出显性电平表示逻辑 0 。
在 can 总线中,必须使它处于隐性电平 (逻辑 1) 或显性电平 (逻辑 0) 中的其中一个状态。假如有两个 can 通讯节点,在同一时间,一个输出隐性电平,另一个输出显性电平,类似 i2c 总线的“线与”特性将使它处于显性电平状态,显性电平的名字就是这样来的,即可以认为显性具有优先的意味。
由于 can 总线协议的物理层只有 1 对差分线,在一个时刻只能表示一个信号,所以对通讯节点来说,can 通讯是半双工的,收发数据需要分时进行。在 can 的通讯网络中,因为共用总线,在整个网络中同一时刻只能有一个通讯节点发送信号,其余的节点在该时刻都只能接收。
1.3 can 协议层
1.3.1 can 的波特率及位同步
由于 can 属于异步通讯,没有时钟信号线,连接在同一个总线网络中的各个节点会像串口异步通讯那样,节点间使用约定好的波特率进行通讯,特别地, can 还会使用“位同步”的方式来抗干扰、吸收误差,实现对总线电平信号进行正确的采样,确保通讯正常。
1.3.2 位时序分解
为了实现位同步,can 协议把每一个数据位的时序分解成如图 所示的 ss 段、pts 段、pbs1 段、pbs2 段,这四段的长度加起来即为一个 can 数据位的长度。分解后最小的时间单位是 tq,而一个完整的位由 8~25 个 tq 组成。为方便表示,图 中的高低电平直接代表信号逻辑 0 或逻辑 1(不是差分信号)。
该图中表示的 can 通讯信号每一个数据位的长度为 19tq,其中 ss 段占 1tq, pts 段占 6tq, pbs1段占 5tq, pbs2 段占 7tq。信号的采样点位于 pbs1 段与 pbs2 段之间,通过控制各段的长度,可以对采样点的位置进行偏移,以便准确地采样。
各段的作用如介绍下:
• ss 段 (sync seg)
ss 译为同步段,若通讯节点检测到总线上信号的跳变沿被包含在 ss 段的范围之内,则表示节点与总线的时序是同步的,当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电平。ss 段的大小固定为 1tq。
• pts 段 (prop seg)
pts 译为传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上输入比较器延时和输出驱动器延时总和的两倍。pts 段的大小可以为 1~8tq。
• pbs1 段 (phase seg1),
pbs1 译为相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。pbs1 段的初始大小可以为 1~8tq。
• pbs2 段 (phase seg2)
pbs2 这是另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。pbs2 段的初始大小可以为 2~8tq。
1.3.3 通讯的波特率
总线上的各个通讯节点只要约定好 1 个 tq 的时间长度以及每一个数据位占据多少个 tq,就可以确定 can 通讯的波特率。
例如,假设上图中的 1tq=1us,而每个数据位由 19 个 tq 组成,则传输一位数据需要时间 t1bit=19us,从而每秒可以传输的数据位个数为:1x10次方/19 = 52631.6 (bps)
这个每秒可传输的数据位的个数即为通讯中的波特率。
1.3.4 同步过程分析
波特率只是约定了每个数据位的长度,数据同步还涉及到相位的细节,这个时候就需要用到数据位内的 ss、pts、pbs1 及 pbs2 段了。根据对段的应用方式差异, can 的数据同步分为硬同步和重新同步。其中硬同步只是当存在“帧起始信号”时起作用,无法确保后续一连串的位时序都是同步的,而重新同步方式可解决该问题,这两种方式具体介绍如下:
(1) 硬同步
若某个 can 节点通过总线发送数据时,它会发送一个表示通讯起始的信号 (即下一小节介绍的帧起始信号),该信号是一个由高变低的下降沿。而挂载到 can 总线上的通讯节点在不发送数据时,会时刻检测总线上的信号。见图 ,可以看到当总线出现帧起始信号时,某节点检测到总线的帧起始信号不在节点内部时序的 ss 段范围,所以判断它自己的内部时序与总线不同步,因而这个状态的采样点采集得的数据是不正确的。所以节点以硬同步的方式调整,把自己的位时序中的 ss 段平移至总线出现下降沿的部分,获得同步,同步后采样点就可以采集得正确数据了。
(2) 重新同步
前面的硬同步只是当存在帧起始信号时才起作用,如果在一帧很长的数据内,节点信号与总线信号相位有偏移时,这种同步方式就无能为力了。因而需要引入重新同步方式,它利用普通数据位的高至低电平的跳变沿来同步 (帧起始信号是特殊的跳变沿)。重新同步与硬同步方式相似的地方是它们都使用 ss 段来进行检测,同步的目的都是使节点内的 ss 段把跳变沿包含起来。重新同步的方式分为超前和滞后两种情况,以总线跳变沿与 ss 段的相对位置进行区分。第一种相位超前的情况如图 ,节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前 2tq,这时控制器在下一个位时序中的 pbs1 段增加 2tq 的时间长度,使得节点与总线时序重新同步。
第二种相位滞后的情况如图 ,节点从总线的边沿跳变中,检测到它的时序比总线的时序相对滞后 2tq,这时控制器在前一个位时序中的 pbs2 段减少 2tq 的时间长度,获得同步。
在重新同步的时候,pbs1 和 pbs2 中增加或减少的这段时间长度被定义为“重新同步补偿宽度sjw* (resynchronization jump width)”。一般来说 can 控制器会限定 sjw 的最大值,如限定了最大 sjw=3tq 时,单次同步调整的时候不能增加或减少超过 3tq 的时间长度,若有需要,控制器会通过多次小幅度调整来实现同步。当控制器设置的 sjw 极限值较大时,可以吸收的误差加大,但通讯的速度会下降
1.3.5 can 的报文种类及结构
在 spi 通讯中,片选、时钟信号、数据输入及数据输出这 4 个信号都有单独的信号线,i2c 协议包含有时钟信号及数据信号 2 条信号线,异步串口包含接收与发送 2 条信号线,这些协议包含的信号都比 can 协议要丰富,它们能轻易进行数据同步或区分数据传输方向。而 can 使用的是两条差分信号线,只能表达一个信号,简洁的物理层决定了 can 必然要配上一套更复杂的协议,如何用一个信号通道实现同样、甚至更强大的功能呢?can 协议给出的解决方案是对数据、操作命令 (如读/写) 以及同步信号进行打包,打包后的这些内容称为报文。
1.3.5.1 报文的种类
在原始数据段的前面加上传输起始标签、片选 (识别) 标签和控制标签,在数据的尾段加上 crc校验标签、应答标签和传输结束标签,把这些内容按特定的格式打包好,就可以用一个通道表达各种信号了,各种各样的标签就如同 spi 中各种通道上的信号,起到了协同传输的作用。当整个数据包被传输到其它设备时,只要这些设备按格式去解读,就能还原出原始数据,这样的报文就被称为 can 的“数据帧”。
为了更有效地控制通讯,can 一共规定了 5 种类型的帧,它们的类型及用途说明如表
1.3.5.2 数据帧的结构
数据帧是在 can 通讯中最主要、最复杂的报文,我们来了解它的结构,见图
数据帧以一个显性位 (逻辑 0) 开始,以 7 个连续的隐性位 (逻辑 1) 结束,在它们之间,分别有仲裁段、控制段、数据段、crc 段和 ack 段。
• 帧起始
sof 段 (start offrame),译为帧起始,帧起始信号只有一个数据位,是一个显性电平,它用于通知各个节点将有数据传输,其它节点通过帧起始信号的电平跳变沿来进行硬同步。
• 仲裁段
当同时有两个报文被发送时,总线会根据仲裁段的内容决定哪个数据包能被传输,这也是它名称的由来。
仲裁段的内容主要为本数据帧的 id 信息 (标识符),数据帧具有标准格式和扩展格式两种,区别就在于 id 信息的长度,标准格式的 id 为 11 位,扩展格式的 id 为 29 位,它在标准 id 的基础上多出 18 位。在 can 协议中, id 起着重要的作用,它决定着数据帧发送的优先级,也决定着其它节点是否会接收这个数据帧。can 协议不对挂载在它之上的节点分配优先级和地址,对总线的占有权是由信息的重要性决定的,即对于重要的信息,我们会给它打包上一个优先级高的 id,使它能够及时地发送出去。也正因为它这样的优先级分配原则,使得 can 的扩展性大大加强,在总线上增加或减少节点并不影响其它设备。报文的优先级,是通过对 id 的仲裁来确定的。根据前面对物理层的分析我们知道如果总线上同时出现显性电平和隐性电平,总线的状态会被置为显性电平,can 正是利用这个特性进行仲裁。
若两个节点同时竞争 can 总线的占有权,当它们发送报文时,若首先出现隐性电平,则会失去对总线的占有权,进入接收状态。见图 ,在开始阶段,两个设备发送的电平一样,所以它们一直继续发送数据。到了图中箭头所指的时序处,节点单元 1 发送的为隐性电平,而此时节点单元 2 发送的为显性电平,由于总线的“线与”特性使它表达出显示电平,因此单元 2 竞争总线成功,这个报文得以被继续发送出去。
仲裁段 id 的优先级也影响着接收设备对报文的反应。因为在 can 总线上数据是以广播的形式发送的,所有连接在 can 总线的节点都会收到所有其它节点发出的有效数据,因而我们的 can
控制器大多具有根据 id 过滤报文的功能,它可以控制自己只接收某些 id 的报文。回看数据帧格式,可看到仲裁段除了报文 id 外,还有 rtr、ide 和 srr 位。
(1) rtr 位 (remote transmission request bit),译作远程传输请求位,它是用于区分数据帧和遥控帧的,当它为显性电平时表示数据帧,隐性电平时表示遥控帧。
(2) ide 位 (identifier extensionbit),译作标识符扩展位,它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格式,隐性电平时表示扩展格式。
(3) srr 位 (substitute remote request bit),只存在于扩展格式,它用于替代标准格式中的 rtr位。由于扩展帧中的 srr 位为隐性位,rtr 在数据帧为显性位,所以在两个 id 相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。
• 控制段
在控制段中的 r1 和 r0 为保留位,默认设置为显性位。它最主要的是 dlc 段 (data length code),译为数据长度码,它由 4 个数据位组成,用于表示本报文中的数据段含有多少个字节, dlc 段表示的数字为 0~8。
• 数据段
数据段为数据帧的核心内容,它是节点要发送的原始信息,由 0~8 个字节组成,msb 先行。
• crc 段
为了保证报文的正确传输,can 的报文包含了一段 15 位的 crc 校验码,一旦接收节点算出的crc 码跟接收到的 crc 码不同,则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。crc 部分的计算一般由 can 控制器硬件完成,出错时的处理则由软件控制最大重发数。在 crc 校验码之后,有一个 crc 界定符,它为隐性位,主要作用是把 crc 校验码与后面的 ack段间隔起来。
• ack 段
ack 段包括一个 ack 槽位,和 ack 界定符位。类似 i2c 总线,在 ack 槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。在 ack 槽和帧结束之间由 ack 界定符间隔开。
• 帧结束
eof 段 (end of frame),译为帧结束,帧结束段由发送节点发送的 7 个隐性位表示结束。
1.3.5.3 其它报文的结构
02
stm32f407 can controller介绍
stm32 的芯片中具有 bxcan 控制器 (basic extended can),它支持 can 协议 2.0a 和 2.0b 标准。该 can 控制器支持最高的通讯速率为 1mb/s;可以自动地接收和发送 can 报文,支持使用标准id 和扩展 id 的报文;外设中具有 3 个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间;具有 2 个 3 级深度的接收 fifo,可使用过滤功能只接收或不接收某些 id 号的报文;可配置成自动重发;不支持使用 dma 进行数据收发。框架示意图如下:
stm32 的有两组 can 控制器,其中 can1 是主设备,框图中的“存储访问控制器”是由 can1控制的,can2 无法直接访问存储区域,所以使用 can2 的时候必须使能 can1 外设的时钟。框图中主要包含 can 控制内核、发送邮箱、接收 fifo 以及验收筛选器,下面对框图中的各个部分进行介绍。
2.1 can 控制内核
框图中标号处的 can 控制内核包含了各种控制寄存器及状态寄存器,我们主要讲解其中的主控制寄存器 can_mcr 及位时序寄存器 can_btr。
2.1.1 主控制寄存器 can_mcr
主控制寄存器 can_mcr 负责管理 can 的工作模式,它使用以下寄存器位实现控制。
(1) dbf 调试冻结功能
dbf(debug freeze) 调试冻结,使用它可设置 can 处于工作状态或禁止收发的状态,禁止收发时仍可访问接收 fifo 中的数据。这两种状态是当 stm32 芯片处于程序调试模式时才使用的,平时使用并不影响。
(2) ttcm 时间触发模式
ttcm(time triggered communication mode) 时间触发模式,它用于配置 can 的时间触发通信模式,在此模式下,can 使用它内部定时器产生时间戳,并把它保存在can_rdtxr、can_tdtxr 寄存器中。内部定时器在每个 can 位时间累加,在接收和发送的帧起始位被采样,并生成时间戳。利用它可以实现 iso 11898-4 can 标准的分时同步通信功能。
(3) abom 自动离线管理
abom (automatic bus-off management) 自动离线管理,它用于设置是否使用自动离线管理功能。当节点检测到它发送错误或接收错误超过一定值时,会自动进入离线状态,在离线状态中, can 不能接收或发送报文。处于离线状态的时候,可以软件控制恢复或者直接使用这个自动离线管理功能,它会在适当的时候自动恢复。
(4) awum 自动唤醒
awum (automatic bus-off management),自动唤醒功能,can 外设可以使用软件进入低功耗的睡眠模式,如果使能了这个自动唤醒功能,当 can 检测到总线活动的时候,会自动唤醒。
(5) nart 自动重传
nart(no automatic retransmission) 报文自动重传功能,设置这个功能后,当报文发送失败时会自动重传至成功为止。若不使用这个功能,无论发送结果如何,消息只发送一次。
(6) rflm 锁定模式
rflm(receive fifo locked mode)fifo 锁定模式,该功能用于锁定接收 fifo 。锁定后,当接收 fifo 溢出时,会丢弃下一个接收的报文。若不锁定,则下一个接收到的报文会覆盖原报文。
(7) txfp 报文发送优先级的判定方法
txfp(transmit fifo priority) 报文发送优先级的判定方法,当 can 外设的发送邮箱中有多个待发送报文时,本功能可以控制它是根据报文的 id 优先级还是报文存进邮箱的顺序来发送。
2.1.2 位时序寄存器 (can_btr) 及波特率
can 外设中的位时序寄存器 can_btr 用于配置测试模式、波特率以及各种位内的段参数。
2.1.2.1 模式
位31 silm:静默模式(调试)(silent mode (debug))
0:正常工作
1:静默模式
位30 lbkm:环回模式(调试)(loop back mode (debug))
0:禁止环回模式
1:使能环回模式
为方便调试,stm32 的 can 提供了测试模式,配置位时序寄存器 can_btr 的 silm 及 lbkm寄存器位可以控制使用正常模式、静默模式、回环模式及静默回环模式,见图。
各个工作模式介绍如下:
• 正常模式
正常模式下就是一个正常的 can 节点,可以向总线发送数据和接收数据。
• 静默模式
静默模式下,它自己的输出端的逻辑 0 数据会直接传输到它自己的输入端,逻辑 1 可以被发送到总线,所以它不能向总线发送显性位 (逻辑 0),只能发送隐性位 (逻辑 1)。输入端可以从总线接收内容。由于它只可发送的隐性位不会强制影响总线的状态,所以把它称为静默模式。这种模式一般用于监测,它可以用于分析总线上的流量,但又不会因为发送显性位而影响总线。
• 回环模式
回环模式下,它自己的输出端的所有内容都直接传输到自己的输入端,输出端的内容同时也会被传输到总线上,即也可使用总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。使用回环模式可以进行自检。
• 回环静默模式
回环静默模式是以上两种模式的结合,自己的输出端的所有内容都直接传输到自己的输入端,并且不会向总线发送显性位影响总线,不能通过总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。这种方式可以在“热自检”时使用,即自我检查的时候,不会干扰总线。
以上说的各个模式,是不需要修改硬件接线的,例如,当输出直接连输入时,它是在 stm32 芯片内部连接的,传输路径不经过 stm32 的 can_tx/rx 引脚,更不经过外部连接的 can 收发器,只有输出数据到总线或从总线接收的情况下才会经过 can_tx/rx 引脚和收发器
2.1.2.2 位时序及波特率
stm32 外设定义的位时序与我们前面解释的 can 标准时序有一点区别,见图
stm32 的 can 外设位时序中只包含 3 段,分别是同步段 sync_seg、位段 bs1 及位段 bs2,采样点位于 bs1 及 bs2 段的交界处。其中 sync_seg 段固定长度为 1tq,而 bs1 及 bs2 段可以
在位时序寄存器 can_btr 设置它们的时间长度,它们可以在重新同步期间增长或缩短,该长度sjw 也可在位时序寄存器中配置。
理解 stm32 的 can 外设的位时序时,可以把它的 bs1 段理解为是由前面介绍的 can 标准协议中 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)
其中clk为42m!
推荐一个can波特率计算器
2.2 can 发送邮箱
回到图 中的 can 外设框图,在标号处的是 can 外设的发送邮箱,它一共有 3 个发送邮箱,即最多可以缓存 3 个待发送的报文。每个发送邮箱中包含有标识符寄存器 can_tixr、数据长度控制寄存器 can_tdtxr 及 2 个数据寄存器 can_tdlxr、can_tdhxr,它们的功能见表
当我们要使用 can 外设发送报文时,把报文的各个段分解,按位置写入到这些寄存器中,并对标识符寄存器 can_tixr 中的发送请求寄存器位 tmidxr_txrq 置 1,即可把数据发送出去。其中标识符寄存器 can_tixr 中的 stdid 寄存器位比较特别。我们知道 can 的标准标识符的总位数为 11 位,而扩展标识符的总位数为 29 位的。当报文使用扩展标识符的时候,标识符寄存器 can_tixr 中的 stdid[10:0] 等效于 extid[18:28] 位,它与 extid[17:0] 共同组成完整的 29位扩展标识符。
2.3 can 接收 fifo
图 中的 can 外设框图,在标号处的是 can 外设的接收 fifo,它一共有 2 个接收 fifo,每个 fifo 中有 3 个邮箱,即最多可以缓存 6 个接收到的报文。当接收到报文时,fifo 的报文计数器会自增,而 stm32 内部读取 fifo 数据之后,报文计数器会自减,我们通过状态寄存器可获知报文计数器的值,而通过前面主控制寄存器的 rflm 位,可设置锁定模式,锁定模式下 fifo溢出时会丢弃新报文,非锁定模式下 fifo 溢出时新报文会覆盖旧报文。跟发送邮箱类似,每个接收 fifo 中包含有标识符寄存器 can_rixr、数据长度控制寄存器can_rdtxr 及 2 个数据寄存器 can_rdlxr、can_rdhxr,它们的功能见表。
通过中断或状态寄存器知道接收 fifo 有数据后,我们再读取这些寄存器的值即可把接收到的报文加载到 stm32 的内存中
2.4 验收筛选器
图 中的 can 外设框图,在标号处的是 can 外设的验收筛选器,一共有 28 个筛选器组,每个筛选器组有 2 个寄存器,can1 和 can2 共用的筛选器的。在 can 协议中,消息的标识符与节点地址无关,但与消息内容有关。因此,发送节点将报文广播给所有接收器时,接收节点会根据报文标识符的值来确定软件是否需要该消息,为了简化软件的工作,stm32 的 can 外设接收报文前会先使用验收筛选器检查,只接收需要的报文到 fifo中。
筛选器工作的时候,可以调整筛选 id 的长度及过滤模式。根据筛选 id 长度来分类有有以下两种:
(1) 检查 stdid[10:0]、extid[17:0]、ide 和 rtr 位,一共 31 位。
(2) 检查 stdid[10:0]、rtr、ide 和 extid[17:15],一共 16 位。
通过配置筛选尺度寄存器 can_fs1r 的 fscx 位可以设置筛选器工作在哪个尺度。而根据过滤的方法分为以下两种模式:
(1) 标识符列表模式,它把要接收报文的 id 列成一个表,要求报文 id 与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理。
(2) 掩码模式,它把可接收报文 id 的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码 (关键字) 相同,就符合要求,报文就会被保存到接收 fifo。通过配置筛选模式寄存器 can_fm1r 的 fbmx 位可以设置筛选器工作在哪个模式。不同的尺度和不同的过滤方法可使筛选器工作在图 的 4 种状态。
每组筛选器包含 2 个 32 位的寄存器,分别为 can_fxr1 和 can_fxr2,它们用来存储要筛选的id 或掩码,各个寄存器位代表的意义与图中两个寄存器下面“映射”的一栏一致,各个模式的说明见表。
例如下面的表格所示,在掩码模式时,第一个寄存器存储要筛选的 id,第二个寄存器存储掩码,掩码为 1 的部分表示该位必须与 id 中的内容一致,筛选的结果为表中第三行的 id 值,它是一组包含多个的 id 值,其中 x 表示该位可以为 1 可以为 0。
而工作在标识符模式时,2 个寄存器存储的都是要筛选的 id,它只包含 2 个要筛选的 id 值 (32位模式时)。
如果使能了筛选器,且报文的 id 与所有筛选器的配置都不匹配,can 外设会丢弃该报文,不存入接收 fifo。
2.5 整体控制逻辑
回到图 结构框图,图中的标号处表示的是 can2 外设的结构,它与 can1 外设是一样的,他们共用筛选器且由于存储访问控制器由 can1 控制,所以要使用 can2 的时候必须要使能can1 的时钟。其中 stm32f103 系列芯片不具有 can2 控制器。
2.6 stm32 hal库代码逻辑
2.6.1 初始化
注意:网络上基本上用的很久的hal库,我们采用很新的1.25.2,最新的库还是差异挺大的!
从 stm32 的 can 外设我们了解到它的功能非常多,控制涉及的寄存器也非常丰富,而使用stm32 hal 库提供的各种结构体及库函数可以简化这些控制过程。跟其它外设一样,stm32
hal 库提供了 can 初始化结构体及初始化函数来控制 can 的工作方式,提供了收发报文使用的结构体及收发函数,还有配置控制筛选器模式及 id 的结构体。这些内容都定义在库文件“stm32f4xx_hal_can.h”及“stm32f4xx_hal_can.c”中,编程时我们可以结合这两个文件内的注释使用或参考库帮助文档。首先我们来学习初始化结构体的内容,见代码清单 1。代码清单 can 初始化结构
typedef struct
{
uint32_t prescaler; /* 配置 can 外设的时钟分频,可设置为 1-1024*/
uint32_t mode; /* 配置 can 的工作模式,回环或正常模式 */
uint32_t syncjumpwidth; /* 配置 sjw 极限值 */
uint32_t timeseg1; /* 配置 bs1 段长度 */
uint32_t timeseg2; /* 配置 bs2 段长度 */
functionalstate timetriggeredmode; /* 是否使能 ttcm 时间触发功能 */
functionalstate autobusoff; /* 是否使能 abom 自动离线管理功能 */
functionalstate autowakeup; /* 是否使能 awum 自动唤醒功能 */
functionalstate autoretransmission; /* 是否使能 nart 自动重传功能 */
functionalstate receivefifolocked; /* 是否使能 rflm 锁定 fifo 功能 */
functionalstate transmitfifopriority;/* 配置 txfp 报文优先级的判定方法 */
} can_inittypedef;
体这些结构体成员说明如下,其中括号内的文字是对应参数在 stm32 hal 库中定义的宏
(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 的优先级来发送。配置完这些结构体成员后,我们调用库函数 hal_can_init 即可把这些参数写入到 can 控制寄存器中,实现 can 的初始化
2.6.2 can 发送及接收结构体
在发送或接收报文时,需要往发送邮箱中写入报文信息或从接收 fifo 中读取报文信息,利用stm32hal 库的发送及接收结构体可以方便地完成这样的工作,它们的定义见代码清单 。代码清单 39‑2 can 发送及接收结构体
typedef struct
{
uint32_t stdid; /* 存储报文的标准标识符 11 位,0-0x7ff. */
uint32_t extid; /* 存储报文的扩展标识符 29 位,0-0x1fffffff. */
uint32_t ide; /* 存储 ide 扩展标志 */
uint32_t rtr; /* 存储 rtr 远程帧标志 */
uint32_t dlc; /* 存储报文数据段的长度,0-8 */
functionalstate transmitglobaltime;
} can_txheadertypedef;
typedef struct
{
uint32_t stdid; /* 存储报文的标准标识符 11 位,0-0x7ff. */
uint32_t extid; /* 存储报文的扩展标识符 29 位,0-0x1fffffff. */
uint32_t ide; /* 存储 ide 扩展标志 */
uint32_t rtr; /* 存储 rtr 远程帧标志 */
uint32_t dlc; /* 存储报文数据段的长度,0-8 */
uint32_t timestamp;
uint32_t filtermatchindex;
} can_rxheadertypedef;
这些结构体成员, 说明如下:
(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。
2.6.3 can 筛选器结构体
can 的筛选器有多种工作模式,利用筛选器结构体可方便配置,它的定义见代码清单 。代码清单can 筛选器结构体
typedef struct
{
uint32_t filteridhigh; /*can_fxr1 寄存器的高 16 位 */
uint32_t filteridlow; /*can_fxr1 寄存器的低 16 位 */
uint32_t filtermaskidhigh; /*can_fxr2 寄存器的高 16 位 */
uint32_t filtermaskidlow; /*can_fxr2 寄存器的低 16 位 */
uint32_t filterfifoassignment; /* 设置经过筛选后数据存储到哪个接收 fifo */
uint32_t filterbank; /* 筛选器编号,范围 0-27,数据手册上说0-27是can1/can2共享,但是实测发现并不是这样,can1是0-13,can2是14-27 */
uint32_t filtermode; /* 筛选器模式 */
uint32_t filterscale; /* 设置筛选器的尺度 */
uint32_t filteractivation; /* 是否使能本筛选器 */
uint32_t slavestartfilterbank;
} can_filtertypedef;
这些结构体成员都是“41.2.14 验收筛选器”小节介绍的内容,可对比阅读,各个结构体成员的介绍如下:
(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)。
03
can cubemx配置
我们通过问题来熟悉下cubemx配置,你熟悉了这些问题基本就知道怎么配置了!
问题:parameter settings分别都是设置什么的?
答案:如图
问题:怎么配置波特率呢?
答案:用我上面贴的工具(can波特率计算 f103ahp1_36m f407ahp1_42m 采样点软件有说明.rar)直接配置,举两个个例子
例子1:我们要配置成500khz,那么我们这样配置
我们用采集点为80%,所以bs1为4tq,bs2为2tq,分频系数为12,代进公式fpclk1/((can_bs1+can_bs2+1)*can_prescaler)=42m/(4+2+1)/12=500khz
例子2:我们要配置成1m hz,那么我们这样配置
我们用采集点为75%,所以bs1为3tq,bs2为2tq,分频系数为7,代进公式fpclk1/((can_bs1+can_bs2+1)*can_prescaler)=42m/(3+2+1)/7=1mhz
问题:basic parameter分别是啥意思呢?
timer triggered communication mode:否使用时间触发功能 (enable/disable),时间触发功能在某些can 标准中会使用到。
automatic bus-off management:用于设置是否使用自动离线管理功能 (enable/disable),使用自动离线管理可以在出错时离线后适时自动恢复,不需要软件干预。
automatic wake-up mode:用于设置是否使用自动唤醒功能 (enable/disable),使能自动唤醒功能后它会在监测到总线活动后自动唤醒。
automatic retransmission:用于设置是否使用自动重传功能 (enable/disable),使用自动重传功能时,会一直发送报文直到成功为止。
receive fifo locked mode:用于设置是否使用锁定接收 fifo(enable/disable),锁定接收 fifo 后,若fifo 溢出时会丢弃新数据,否则在 fifo 溢出时以新数据覆盖旧数据。
transmit fifo priority:用于设置发送报文的优先级判定方法 (enable/disable),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文 id 的优先级来发送。配置完这些结构体成员后,我们调用库函数 hal_can_init 即可把这些参数写入到 can 控制寄存器中,实现 can 的初始化
问题:为啥can分为rx0,rx1中断呢?
答案:stm32有2个3级深度的接收缓冲区:fifo0和fifo1,每个fifo都可以存放3个完整的报文,它们完全由硬件来管理。如果是来自fifo0的接收中断,则用can1_rx0_irqn中断来处理。如果是来自fifo1的接收中断,则用can1_rx1_irqn中断来处理,如图:
问题:can sce中断时什么?
答案:status chanege error,错误和状态变化中断!
04
实验
1.normal模式测试500k 波特率(定时发送,轮询接收)
1.1 cubemx配置
1.2 设置filter过滤,我们只使能fifo0,并且不过滤任何消息
uint8_t bsp_can1_filter_config(void)
{
can_filtertypedef filter = {0};
filter.filteractivation = enable;
filter.filtermode = can_filtermode_idmask;
filter.filterscale = can_filterscale_32bit;
filter.filterbank = 0;
filter.filterfifoassignment = can_filter_fifo0;
filter.filteridlow = 0;
filter.filteridhigh = 0;
filter.filtermaskidlow = 0;
filter.filtermaskidhigh = 0;
hal_can_configfilter(&hcan1, &filter);
return bsp_can_ok;
}
1.3 开启can(注意,默认cubemx生成的代码并没有can start)
hal_can_start(&hcan1);
1.4 编写发送函数
我们开出了几个参数,id_type是扩展帧还是标准帧,basic_id标准帧id(在标准帧中有效),ex_id扩展帧id(在扩展帧中有效),data要发送的数据,data_len要发送的数据长度
uint8_t bsp_can1_send_msg(uint32_t id_type,uint32_t basic_id,uint32_t ex_id,uint8_t *data,uint32_t data_len)
{
uint8_t index = 0;
uint32_t *msg_box;
uint8_t send_buf[8] = {0};
can_txheadertypedef send_msg_hdr;
send_msg_hdr.stdid = basic_id;
send_msg_hdr.extid = ex_id;
send_msg_hdr.ide = id_type;
send_msg_hdr.rtr = can_rtr_data;
send_msg_hdr.dlc = data_len;
send_msg_hdr.transmitglobaltime = disable;
for(index = 0; index < data_len; index++)
send_buf[index] = data[index];
hal_can_addtxmessage(&hcan1,&send_msg_hdr,send_buf,msg_box);
return bsp_can_ok;
}
我们在main函数中1s发送一帧,标准帧跟扩展帧交叉调用,代码如下:
send_data[0]++;
send_data[1]++;
send_data[2]++;
send_data[3]++;
send_data[4]++;
send_data[5]++;
send_data[6]++;
send_data[7]++;
if(id_type_std == 1)
{
bsp_can1_send_msg(can_id_std,1,2,send_data,8);
id_type_std = 0;
}
else
{
bsp_can1_send_msg(can_id_ext,1,2,send_data,8);
id_type_std = 1;
}
hal_delay(1000);
我们通过can协议分析仪来抓下结果
1.5 编写轮询接收函数
uint8_t bsp_can1_polling_recv_msg(uint32_t *basic_id,uint32_t *ex_id,uint8_t *data,uint32_t *data_len)
{
uint8_t index = 0;
uint8_t recv_data[8];
can_rxheadertypedef header;
while (hal_can_getrxfifofilllevel(&hcan1, can_rx_fifo0) != 0)
{
if (__hal_can_get_flag(&hcan1, can_flag_fov0) != reset)
printf([can] fifo0 overrun! );
hal_can_getrxmessage(&hcan1, can_rx_fifo0, &header, recv_data);
if(header.ide == can_id_std)
{
printf(stdid id:%d ,header.stdid);
}
else
{
printf(extid id:%d ,header.extid);
}
printf(can ide:0x%x ,header.ide);
printf(can rtr:0x%x ,header.rtr);
printf(can dlc:0x%x ,header.dlc);
printf(recv data:);
for(index = 0; index < header.dlc; index++)
{
printf(0x%x ,recv_data[index]);
}
printf( );
}
}
实验一总结:
1.没用调用hal_can_start(&hcan1);使能can
2.没有编写filter函数,我开始自认为不设置就默认不过滤,现在看来是我想多了,其实想想也合理,你如果不过滤分配fifo,stm32怎么决定把收到的放到哪个fifo中
待提升:
1.目前只用到fifo0,待把fifo1使用起来
2.normal模式测试500k 波特率(定时发送,中断接收)
2.1 cubemx配置
步骤2,3,4跟polling完全一致,我们来直接说下中断怎么用(主要是使能notifity就行了)
static void mx_can1_init(void)
{
/* user code begin can1_init 0 */
/* user code end can1_init 0 */
/* user code begin can1_init 1 */
/* user code end can1_init 1 */
hcan1.instance = can1;
hcan1.init.prescaler = 12;
hcan1.init.mode = can_mode_normal;
hcan1.init.syncjumpwidth = can_sjw_1tq;
hcan1.init.timeseg1 = can_bs1_4tq;
hcan1.init.timeseg2 = can_bs2_2tq;
hcan1.init.timetriggeredmode = disable;
hcan1.init.autobusoff = enable;
hcan1.init.autowakeup = enable;
hcan1.init.autoretransmission = disable;
hcan1.init.receivefifolocked = disable;
hcan1.init.transmitfifopriority = disable;
if (hal_can_init(&hcan1) != hal_ok)
{
error_handler();
}
/* user code begin can1_init 2 */
bsp_can1_filter_config();
hal_can_start(&hcan1);
hal_can_activatenotification(&hcan1,can_it_rx_fifo0_msg_pending);
/* user code end can1_init 2 */
}
下面我们来编写下中断函数
void hal_can_rxfifo0msgpendingcallback(can_handletypedef *hcan)
{
uint8_t index = 0;
uint8_t recv_data[8];
can_rxheadertypedef header;
hal_can_getrxmessage(&hcan1, can_rx_fifo0, &header, recv_data);
if(header.ide == can_id_std)
{
printf(stdid id:%d ,header.stdid);
}
else
{
printf(extid id:%d ,header.extid);
}
printf(can ide:0x%x ,header.ide);
printf(can rtr:0x%x ,header.rtr);
printf(can dlc:0x%x ,header.dlc);
printf(recv data:);
for(index = 0; index < header.dlc; index++)
{
printf(0x%x ,recv_data[index]);
}
printf( );
}
参考内容:
1.[野火embedfire]《stm32 hal库开发实战指南——基于野火挑战者开发板》.pdf
2.stm32f4参考手册.pdf
3.can入门书.pdf
西门子WinCC 7.5最新安装详细步骤
关于典型运算放大器电路的操作
编码器音频功能
铁氧体磁珠的原理及应用
关于S7-300 PLC的50个常见问题
can总线协议详解:CAN 的报文种类及结构
智能工厂管理深思 重点不是自动化
怎样修复损坏的GM钥匙扣
神奇环保 LED让绿色照明“动”起来
国产GPU厂商名录
电源滤波器是什么,有哪些作用
数字货币禁令的观点对国会意味着什么
PLC为何会成为工业智慧照明的首选?
Cirrus Logic助PC行业向全新 MIPI SoundWire® 接口实现轻松过渡
边缘计算到底是什么
聊聊雷达参数测量的那些事儿
打电话的时候通信讯号总是断断续续的怎么办?
AI男友系统 连你的呼吸都懂
运算放大器测试基础第4部分:测试运算放大器需要稳定的测试环路
关于PLD的分类及其优点分析