一、CAN简介
- CAN总线(Controller Area Network Bus)控制器局域网总线
- CAN总线是由BOSCH公司开发的一种简洁易用、传输速度快、易扩展、可靠性高的串行通信总线,广泛应用于汽车、嵌入式、工业控制等领域
- CAN总线特征:
硬件电路
- 每个设备通过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Ω的终端电阻
CAN总线传输的为差分信号,一般使用双绞线作为载体,避免干扰;
高速CAN中添加的120Ω电阻的作用:防止回波反射、在没有设备操作时,使两端电压一致,即逻辑1
1、CAN电平标准
- CAN总线采用差分信号,即两线电压差(VCAN_H-VCAN_L)传输数据位
- 高速CAN规定:
电压差为0V时表示逻辑1(隐性电平)
电压差为2V时表示逻辑0(显性电平)
- 低速CAN规定:
电压差为-1.5V时表示逻辑1(隐性电平)
电压差为3V时表示逻辑0(显性电平)
2、CAN收发器 – TJA1050(高速CAN)
TJA1050引脚描述:
- TXD:描述传输数据输入;从CAN控制器读入数据到总线驱动器
- GND:地
- Vcc:电源电压(GND和VCC需提供5V供电)
- RXD:接收数据输出;从总线线路读出数据到CAN控制器
- Vref:参考电压输出
- CANL:低级CAN总线
- CANH:高级CAN总线
- S:选择输入高速模式/静音模式
在框图中,连接CANH和CANL的接收器负责检测电源差,并输出至左侧线:有电压差输出1,无电压差输出0; 随后通过两个场效应管的输出驱动器(作用为反相器)输出至RXD引脚:输入1则输出0,输入0则输出1;则:CAN总线无电压差时,输出RXD引脚为高电平1 ,表示隐形电平;
输出部分:档TXD置1时,驱动器会让两个场效应管断开,在外部闭环电路中会呈现默认的隐性电平,CANH和CANL旁边的两个电阻,可将CANH和CANL都拉到0.5*Vcc的中间电平;当TXD置0时,驱动器会让两个场效应管导通,则上方的CANH电压拉高,下面的CANL拉低,产生电压差,总线成显性电平0的状态;
TXD旁边为电流源上拉:若TXD悬空,则保持默认输入1的状态,以防止输入引脚电平不确定造成误操作;
电流源上拉右侧为TXD显性超时计时器:在TXD出现异常,始终输入显性电平0时, 为防止CAN总线始终呈现显性电平0状态导致总线瘫痪,收发器会自动释放总线;
总线帧格式
1、数据帧
发送设备主动发送数据,为广播式通讯方式
图中的数字表示该段时序所占的位数
- 灰色部分D:显性电平0
- 紫色部分D/R:根据发送数据,选择显性/隐性电平
- 白色部分R:隐性电平1
- 白色、灰色各一半:应答位,发送方必须发隐性电平1,释放总线;接收方必须发显性电平0,拉开总线
在发送数据帧之前,总线需处于空闲状态,即隐性电平R,高电平;
标准格式:
帧起始SOF:数据帧第一位,为低电平,打破总线空闲;
仲裁段:
- 11位报文ID:用于区分优先级,多个设备同时发送时,根据仲裁规则,ID小的报文优先发送;
- RTR:1位远程请求标志位,用于区分数据帧or遥控帧,在数据帧中为显性0,遥控帧中为隐性1;对于相同相同ID设备,数据帧的优先级大于遥控帧;
控制段:
- IDE:ID控制标志位,用于区分标准格式or扩展格式,标准格式为显性0,扩展格式为隐性1
- r0:保留位,尚未使用
- DLC:用于表示数据段的长度,1个字节时为0001,8个字节为1000,且最大为8位
数据段Data:有效数据,DLC指定多少字节,则数据段发送多少字节
CRC段:
- CRC循环冗余校验位:15位校验位,由发送节点根据数据帧的内容(包括起始段、仲裁段、控制段、数据段等)计算出CRC值,由CAN控制器自动生成
- CRC界定符:1位,且为隐性电平
ACK段:
- ACK槽:1位,当接收方(可为多个接收方)收到数据时,则会将总线拉至显性0状态;发送方释放总线后,会在ACK槽读取总线状态,读到显性0则为被接收成功,读到隐性1则为被接受失败,则可配置自动重发
- ACK界定符:1位
帧结束:7个隐性1作为EOF,帧结束,即停止位
扩展格式:用于标准格式11位ID不够用的情况
SRR位:替代RTR的位,在仲裁时,先比较ID位,后比较RTR位,在扩展格式中,RTR移至扩展ID号后面,但SRR位不可使用,必须置隐性电平1
IDE位:标准格式为显性0,扩展格式为隐性1
r1/r0:保留位,尚未使用
2、遥控帧
遥控帧无数据段,RTR为隐性电平1,其他部分与数据帧相同;类比IIC通信的指定地址读/写:一般用于使用频率较低的数据传输,使用频率高的数据可直接使用广播模式
当某个设备发出遥控帧,表示像请求该数据;
一次完整的请求需要遥控帧和数据帧配合:请求方发送遥控帧,遥控帧的ID表示要请求的数据,响应请求的乙方,通过相同ID的数据帧反馈数据;当数据帧和遥控帧同时发生时,数据帧拥有更高的优先级
3、错误帧
总线上所有设备都会监督总线的数据,一旦发现“位错误”或“填充错误”或“CRC错误”或“格式错误”或“应答错误” ,这些设备便会发出错误帧来破坏数据,同时终止当前的发送设备
设备默认属于主动错误状态,当检测出错误时,会连续发送6个显性位,即下拉总线,打断其他正在发送的数据;当主动错误状态触发过于频繁时,则会触发被动错误状态:连续发送6个隐性位,不碰总线,打断本设备发送的数据,但不打断其他设备发送的数据;
发送完错误标志位后跟8个隐性1,作为错误界定符;
一个设备发出的错误标志可能会引发其他设备连带产生错误标志,多个错误标志叠加起来,则可能产生延长的0~6位的错误标志位;
4、过载帧
当接收方收到大量数据而无法处理时,其可以发出过载帧,延缓发送方的数据发送,以平衡总线负载,避免数据丢失
5、帧间隔
帧间隔一样分为主动错误状态和被动错误状态;主动错误状态的帧间隔位3位,被动错误状态的帧间隔是3位+8位延迟传输,表示设备不太可靠
6、位填充
- 位填充规则:发送方每发送5个相同电平后,自动追加一个相反电平的填充位,接收方检测到填充位时,会自动移除填充位,恢复原始数据
- 例如:
即将发送: 100000110 10000011110 0111111111110
实际发送: 1000001110 1000001111100 011111011111010
实际接收: 1000001110 1000001111100 011111011111010
移除填充后: 100000110 10000011110 0111111111110
- 位填充作用:
- 增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机
- 将正常数据流与“错误帧”和“过载帧”区分开,标志“错误帧”和“过载帧”的特异性
- 保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲
接收方数据采样
- CAN总线没有时钟线,总线上的所有设备通过约定波特率的方式确定每一个数据位的时长
- 发送方以约定的位时长每隔固定时间输出一个数据位
- 接收方以约定的位时长每隔固定时间采样总线的电平,输入一个数据位
- 理想状态下,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近
1、位时序
为了灵活调整每个采样点的位置,使采样点对齐数据位中心附近,CAN总线对每一个数据位的时长进行了更细的划分,分为同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每个段又由若干个最小时间单位(Tq)构成(由自己确定)
同步段(SS):若数据跳变沿正好出现在SS段,则说明当前设备与波形达成同步;若不在,则需调整位时序(同步),使跳变沿正好出现在同步段;
传播时间段(PTS):用于吸收网络上的物理延迟(发送单元的输出延迟、总线上信号的传播延迟、接收单位的输入延迟),PTS的时间需为延迟时间的和的两倍;
相位缓冲段PBS1、PBS2:用于确定采样点的位置,采样点的位置在二者中间;
2、硬同步(初始位置同步
- 每个设备都有一个位时序计时周期,当某个设备(发送方)率先发送报文,其他所有设备(接收方)收到SOF(第一个数据跳变边沿)的下降沿时,接收方会将自己的位时序计时周期拨到SS段的位置,与发送方的位时序计时周期保持同步
- 硬同步只在帧的第一个下降沿(SOF下降沿)有效
- 经过硬同步后,若发送方和接收方的时钟没有误差,则后续所有数据位的采样点必然都会对齐数据位中心附近
3、再同步(接收方
- 若发送方或接收方的时钟有误差,随着误差积累,数据位边沿逐渐偏离SS段,则此时接收方根据再同步补偿宽度值(SJW=1~4Tq)通过加长PBS1段,或缩短PBS2段,以调整同步;
- 再同步可以发生在第一个下降沿之后的每个数据位跳变边沿;
- SJW为补偿的最大值,实际使用中被,具体补偿多少个Tq由误差大小和SJW共同决定:若误差值小于等于SJW,则误差多少Tq,补偿多少Tq;若大于SJW,则补偿SJW指定的Tq值,亿避免补偿过度;
4、波特率计算
- 波特率 = 1 / 一个数据位的时长 = 1 / (TSS + TPTS + TPBS1 + TPBS2)
- 例如:
SS = 1Tq,PTS = 3Tq,PBS1 = 3Tq,PBS2 = 3Tq
Tq = 0.5us
波特率 = 1 / (0.5us + 1.5us + 1.5us + 1.5us) = 200kbps
多设备同时发送
制定资源分配规则,依次满足多个设备的发送需求,确保同一时间只有一个设备操作总线
1、资源分配规则1 - 先占先得
- 若当前已经有设备正在操作总线发送数据帧/遥控帧,则其他任何设备不能再同时发送数据帧/遥控帧(可以发送错误帧/过载帧破坏当前数据),即:不存在“抢占优先级”;
- 任何设备检测到连续11个隐性电平,即认为总线空闲,只有在总线空闲时,设备才能发送数据帧/遥控帧;
- 一旦有设备正在发送数据帧/遥控帧,总线就会变为活跃状态,必然不会出现连续11个隐性电平,其他设备自然也不会破坏当前发送;
- 若总线活跃状态其他设备有发送需求,则需要等待总线变为空闲,才能执行发送需求;
2、资源分配规则2 - 非破坏性仲裁
- 若多个设备的发送需求同时到来或因等待而同时到来,则CAN总线协议会根据ID号(仲裁段)进行非破坏性仲裁,ID号小的(优先级高)取到总线控制权,ID号大的(优先级低)仲裁失利后将转入接收状态,等待下一次总线空闲时再尝试发送;
- 实现非破坏性仲裁需要两个要求:
1.线与特性:总线上任何一个设备发送显性电平0时,总线就会呈现显性电平0状态,只有当所有设备都发送隐性电平1时,总线才呈现隐性电平1状态,即:0 & X & X = 0,1 & 1 & 1 = 1
2.回读机制:每个设备发出一个数据位后,都会读回总线当前的电平状态,以确认自己发出的电平是否被真实地发送出去了,根据线与特性,发出0读回必然是0,发出1读回不一定是1
- 数据帧和遥控帧ID号一样时,数据帧的优先级高于遥控帧;
- 标准格式11位ID号和扩展格式29位ID号的高11位一样时,标准格式的优先级高于扩展格式(SRR必须始终为1,以保证此要求)
错误处理
1、错误类型
错误共有5种: 位错误(回读机制)、填充错误、CRC错误、格式错误、应答错误
错误通知:当检测单元检测出错误时,会主动发出错误帧,破坏当前总线上的数据
2、错误状态
- 主动错误状态的设备正常参与通信并在检测到错误时发出主动错误帧,可破坏其他设备的数据帧
- 被动错误状态的设备正常参与通信但检测到错误时只能发出被动错误帧
- 总线关闭状态的设备不能参与通信
- 每个设备内部管理一个TEC和REC,根据TEC和REC的值确定自己的状态
设备在发送时,每发现一个错误,则TEC会对应错误计数器增加,每进行一次正常的发送,则-1;REC同理
3、错误计数器
二、STM32 CAN外设
简介
- STM32内置bxCAN外设(CAN控制器),支持CAN2.0A和2.0B,可以自动发送CAN报文和按照过滤器自动接收指定CAN报文,程序只需处理报文数据而无需关注总线的电平细节
- 波特率最高可达1兆位/秒
- 3个可配置优先级的发送邮箱
- 2个3级深度的接收FIFO
- 14个过滤器组(互联型28个),只接受需要的ID的报文
- 时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置、发送优先级可配置、双CAN模式
CAN框图
CAN基本结构
CAN_TX为输出,引脚配置为复用推挽输出模式;CAN_RX为输入,配置为上拉输入模式;
信号进入后由发送和接收控制器自动管理:
发送部分:将报文的各参数(ID、DAta、IDE、RTR)写入其中一个发送邮箱,并给出请求发送的命令,发送和接受控制器就会在总线空闲时自动将报文广播到总线上; 为防止总线繁忙造成发送拥堵和CPU等待,STM32设置了3个发送邮箱(0、1、2)用作“报文缓存区”,当三个邮箱都有报文且CPU还想发送时,CPU只能等待或放弃,发送邮箱可配置发送选择策略:先请求先发送or按ID号优先级发送;
接收部分:在发送和控制接收器收到报文后,会来到接收过滤器(14个),接受过滤器可以根据ID号对报文进行过滤,若报文的ID号与接收过滤器中的ID号不存在一致,则无法通过过滤器,报文被直接丢弃,反之,则通过其中一个过滤器进入接收FIFO(2个FIFO都有3个接收邮箱);
1、发送过程
基本流程:选择一个空置邮箱→写入报文 →请求发送
相关寄存器:RQCP:请求完成标志位
TXOK:发送成功标志位
TME:发送邮箱空标志位 1为空置状态
TXRQ:发送请求控制位 1为产生发送请求
NART:禁止自动重传 0为使用自动重传,1禁止
ABRQ:终止发送标志位 1为终止发送
TXFP:置1,优先级由发送请求的顺序来决定,先请求的先发送;置0,优先级由报文标识符来决定,标识符值小的先发送
邮箱默认为空置状态,写入报文后请求发送TXRQ置1,邮箱进入挂号状态(RQCP=0、TXOK=0、TME=0),表示邮箱数据准备就绪;若此时邮箱已达最高优先级,则进入预定状态,反之退回挂号状态;进入预定状态后,一旦出现CAN总线=IDLE空闲,则管理员将报文发送至CAN总线,邮箱进入发送状态;若报文正常发送成功,则邮箱回到空置状态,并将标志位改为:RQCP=1、TXOK=1、TME=1;若报文发送失败,且NART=0,则发送失败的报文回到预定状态,等待下次发送,直至发送成功,若NART=1,则失败后直接回到空置状态;
其中,在挂号、预定和发送状态中,若终止发送标志位ABRQ置1,则打断发送,邮箱直接进入空置状态;
2、接收过程
基本流程:接收到一个报文→匹配过滤器后进入FIFO 0或FIFO 1→CPU读取
相关寄存器:FMP:报文数目
FOVR:FIFO溢出
FULL:FIFO存满时置1
RFOM:释放邮箱,置1时释放
RFLM:置1,接收FIFO锁定,FIFO溢出时,新收到的报文会被丢弃;置0,禁用FIFO锁定,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖
初始时FIFO为空状态(FMP=0、FOVR=0);当收到有效报文时,FIFO队列排队一个报文,FIFO进入挂号_1状态(FMP=0x01、FOVR=0),又收到有效报文时,FIFO队列+1,进入挂号_2状态(FMP=0x02、FOVR=0),再收到有效报文时,FIFO队列+1,进入挂号_3状态(FMP=0x03、FOVR=0);此时若还收到有效报文,则进入溢出状态(FMP=0x03、FOVR=1);
若FIFO处于溢出状态,且RFOM=1,则释放邮箱,FIFO直接进入挂号2状态;读取FIFO后依旧置RFOM为1,释放邮箱,则FIFO进入挂号1状态,以此类推可进入空状态
3、标识符过滤器
每个过滤器的核心由两个32位寄存器组成:xR1[31:0]和xR2[31:0]
- FSCx:位宽设置 置0,16位;置1,32位
- FBMx:模式设置 置0,屏蔽模式;置1,列表模式
- FFAx:关联设置 置0,FIFO 0;置1,FIFO 1
- FACTx:激活设置 置0,禁用;置1,启用
工作状态2,2个32位过滤器——标识符列表模式:
R1、R2两个寄存器都写入目标ID,其中32位寄存器的高 11位存入标准格式的ID号STID,后面的18位则存入扩展ID号EXID; 写入标准ID时直接写入高11位的STID空间中,后18位EXID写0,IDE扩展标志位置0即可,扩展ID则在STID和EXID共29位空间中写入,IDE扩展标志位置1;RTR标志位用于过滤数据帧or遥控帧;这种模式下一个过滤器只能过滤两个标准/扩展ID号;
工作状态4,4个16位过滤器——标识符列表模式:
R1、R2两个32位寄存器会被拆成四个16位寄存器,每个寄存器写入一个标准格式的目标ID,此时IDE固定为0,后三位置0即可;
工作状态1,1个32位过滤器——标识符屏蔽模式:
在 R1寄存器中写入一个ID号,R2寄存器中写入屏蔽位(掩码),其数据位中写1的位表示R1中ID对应位必须匹配一致,写0则为无关位,写入1/0都无影响;可使用个扩展位;
工作状态3,2个16位过滤器——标识符屏蔽模式:
与工作状态4类似,将2个32位寄存器拆成4个16位寄存器,其中高16位为ID号,低16位为掩码;
其中,16位寄存器映像为左对齐,赋值语句默认为右对齐,故写入16位ID时需左移5位
测试模式
- 静默模式:用于分析CAN总线的活动,不会对总线造成影响
- 环回模式:用于自测试,同时发送的报文可以在CAN_TX引脚上检测到
- 环回静默模式:用于热自测试,自测的同时不会影响CAN总线
工作模式
- 初始化模式:用于配置CAN外设,禁止报文的接收和发送
- 正常模式:配置CAN外设后进入正常模式,以便正常接收和发送报文
- 睡眠模式:低功耗,CAN外设时钟停止,可使用软件唤醒或者硬件自动唤醒
- AWUM:置1,自动唤醒,一旦检测到CAN总线活动,硬件就自动清零SLEEP,唤醒CAN外设;置0,手动唤醒,软件清零SLEEP,唤醒CAN外设
SLAK,睡眠确认状态位:=1时表示硬件确认进入睡眠模式
INAK,初始化确认位:=1时表示硬件确认进入初始化模式
SLEEP,睡眠模式请求:=1时请求进入睡眠模式,=0时请求推出睡眠模式
INRQ,初始化模式请求:=1请求进入初始化模式,=0请求推出初始化
复位后CAN外设默认是睡眠模式(SLAK=1,INAK=0),在SLEEP置0、INRQ置0后等待ACK信号,应答后SLAK置0,进入正常模式(SLAK=0,INAK=0);反之,正常模式在SLEEP位置1后,等待ACK信号,应答后SLAK置1,确认进入睡眠模式;
睡眠模式在SLEEP置0、INRQ置1后等待ACK信号,应答后SLAK置0、INAK置1,进入初始化模式(SLAK=0,INAK=1);反之,初始化模式在SLEEP位置1、INAK置0后,等待ACK信号,应答后SLAK置1,确认进入睡眠模式;
正常模式和初始化模式之间的转换同理;
位时间特性
波特率 = APB1时钟频率 / 分频系数 / 一位的Tq数量
= 36MHz / (BRP[9:0]+1) / (1 + (TS1[3:0]+1) + (TS2[2:0]+1))
SS = 1Tq BS1 = 1~16Tq BS2 = 1~8Tq SJW=1~4Tq
中断
CAN外设占用4个专用的中断向量:
- 发送中断:发送邮箱空时产生
- FIFO 0中断:收到一个报文/FIFO 0满/FIFO 0溢出时产生
- FIFO 1中断:收到一个报文/FIFO 1满/FIFO 1溢出时产生
- 状态改变错误中断:出错/唤醒/进入睡眠时产生
时间触发通信:
可对所有节点进行同步调度,每个节点只在一个固定的时间段内发送报文,可以避免优先级仲裁
- TTCM:置1,开启时间触发通信功能;置0,关闭时间触发通信功能
- CAN外设内置一个16位的计数器,用于记录时间戳
- TTCM置1后,该计数器在每个CAN位的时间自增一次,溢出后归零
- 每个发送邮箱和接收FIFO都有一个TIME[15:0]寄存器,发送帧SOF时,硬件捕获计数器值到发送邮箱的TIME寄存器,接收帧SOF时,硬件捕获计数器值到接收FIFO的TIME寄存器
- 发送邮箱可配置TGT位,捕获计数器值的同时,也把此值写入到数据帧数据段的最后两个字节,为了使用此功能,DLC必须设置为8
错误处理和离线恢复
- TEC和REC根据错误的情况增加或减少
- ABOM:置1,开启离线自动恢复,进入离线状态后,就自动开启恢复过程;置0,关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启
三、代码实现
流程:
1、初始化:
1、RCC时钟初始化,开启CAN引脚的GPIO和CAN1的时钟;
2、初始化GPIO口:CAN_TX初始化为复用推挽输出,配置为复用推挽输出;CAN_RX初始化为上拉输入;
3、配置CAN外设;
4、初始化过滤器;
5、若需开启中断,则使能中断
2、发送报文
将报文数据写入发送结构体,调用发送函数;
3、接收报文
若FIFO中有报文,则调用接收函数,将报文数据存入接收的结构体中
CAN相关库函数
//把CAN配置到默认的复位状态
void CAN_DeInit(CAN_TypeDef* CANx);
//指定CAN1还是CAN2,以及指定结构体
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
//过滤器初始化
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
//给结构体赋默认值
void CAN_StructInit(CAN_InitTypeDef* CAN_InitStruct);
//用于配置CAN2的起始滤波器号;用于互联型
void CAN_SlaveStartBank(uint8_t CAN_BankNumber);
//用于调试时的冻结模式
void CAN_DBGFreeze(CAN_TypeDef* CANx, FunctionalState NewState);
//用于使能TTCM模式中的TGT位
void CAN_TTComModeCmd(CAN_TypeDef* CANx, FunctionalState NewState);
发送相关
//发送一个CAN报文,需在结构体中提前写入待发送报文的各个数据;返回值为报文存入了哪个发送邮箱
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
//获取发送邮箱的状态
uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox);
//取消发送
void CAN_CancelTransmit(CAN_TypeDef* CANx, uint8_t Mailbox);
接收部分
//读取接收FIFO的数据,传入的结构体会在推出后获得接收到的报文数据,且会自动释放邮箱
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
//释放FIFO,置RFOM=1时可释放邮箱
void CAN_FIFORelease(CAN_TypeDef* CANx, uint8_t FIFONumber);
//获取指定FIFO队列里排队的报文数量,即获取寄存器FMP中的值
uint8_t CAN_MessagePending(CAN_TypeDef* CANx, uint8_t FIFONumber);
工作模式相关:即初始化、正常、睡眠、AWUM、唤醒模式
//工作模式请求,可指定初始化、正常或睡眠模式
uint8_t CAN_OperatingModeRequest(CAN_TypeDef* CANx, uint8_t CAN_OperatingMode);
//直接指定CAN进入睡眠模式
uint8_t CAN_Sleep(CAN_TypeDef* CANx);
//直接指定CAN退出睡眠模式
uint8_t CAN_WakeUp(CAN_TypeDef* CANx);
错误管理相关:
//获取最近一次的错误码,返回值为错误类型
uint8_t CAN_GetLastErrorCode(CAN_TypeDef* CANx);
//获取接受错误计数器
uint8_t CAN_GetReceiveErrorCounter(CAN_TypeDef* CANx);
//获取发送错误计数器的低八位
uint8_t CAN_GetLSBTransmitErrorCounter(CAN_TypeDef* CANx);
中断和标志位管理函数:
//中断使能
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState);
//获取标志位状态
FlagStatus CAN_GetFlagStatus(CAN_TypeDef* CANx, uint32_t CAN_FLAG);
//清除标志位
void CAN_ClearFlag(CAN_TypeDef* CANx, uint32_t CAN_FLAG);
//获取中断状态
ITStatus CAN_GetITStatus(CAN_TypeDef* CANx, uint32_t CAN_IT);
//清除中断挂起位
void CAN_ClearITPendingBit(CAN_TypeDef* CANx, uint32_t CAN_IT);
实现代码:
环回模式:
main:
#include "stm32f10x.h" // Device header
#include "delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
#include "MyCAN.h"
int main(void)
{
delay_init();
OLED_Init();
MyCAN_Init();
KEY_Init();
OLED_ShowString(2, 1, "RxID:");
OLED_ShowString(3, 1, "Lenth:");
OLED_ShowString(4, 1, "Data:");
uint8_t KeyNum;
uint32_t RxID = 0x555;
uint8_t TxLentg = 4;
uint8_t TxData[8] = {0x66, 0x88};
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
MyCAN_Transmit(RxID, TxLentg, TxData);
}
if(MyCAN_ReceiveFlag())
{
uint32_t RxID;
uint8_t RxLenth;
uint8_t RxData[8];
MyCAN_Receive(&RxID, &RxLenth, RxData);
OLED_ShowHexNum(2, 6, RxID, 3);
OLED_ShowHexNum(3, 6, RxLenth, 1);
OLED_ShowHexNum(4, 6, RxData[0], 2);
OLED_ShowHexNum(4, 9, RxData[1], 2);
}
}
}
MyCAN.c:
#include "stm32f10x.h" // Device header
#include "MyCAN.h"
void MyCAN_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
CAN_InitTypeDef CAN_InitStructure;
CAN_InitStructure.CAN_ABOM = DISABLE; //离线手动恢复
CAN_InitStructure.CAN_AWUM = DISABLE; //手动唤醒
CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack; //环回模式
CAN_InitStructure.CAN_NART = DISABLE; //开启自动重传
CAN_InitStructure.CAN_Prescaler = 48; //波特率=36M / 48 / (1+2+3) = 125k 无需手动-1
CAN_InitStructure.CAN_RFLM = DISABLE; //FIFO锁定,最后收到的报文被新报文覆盖
CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
CAN_InitStructure.CAN_TTCM = DISABLE; //关闭时间触发通信模式
CAN_InitStructure.CAN_TXFP = DISABLE; //不开启先请求先发送
CAN_Init(CAN1, &CAN_InitStructure);
CAN_FilterInitTypeDef CAN_FilterInitStructure; //全部报文通过,并存入FIFO0
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInit(&CAN_FilterInitStructure);
}
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)
{
CanTxMsg TxMessage;
TxMessage.StdId = ID; //标准ID
TxMessage.ExtId = ID; //扩展ID
TxMessage.IDE = CAN_Id_Standard; //扩展标志位
TxMessage.RTR = CAN_RTR_Data; //遥控标志位
TxMessage.DLC = Length; //数据段长度
for(uint8_t i=0; i<Length; i++)
{
TxMessage.Data[i] = Data[i]; //数据段内容
}
//返回值为数据被写入哪个邮箱
uint8_t TransmitMailbox = CAN_Transmit(CAN1, &TxMessage); //自动写入邮箱
uint32_t Timeout = 0;
while(CAN_TransmitStatus(CAN1, TransmitMailbox) != CAN_TxStatus_Ok)
{
Timeout++;
if(Timeout > 100000)
{
break;
}
}
}
//查询接收
uint8_t MyCAN_ReceiveFlag(void)
{
if(CAN_MessagePending(CAN1, CAN_FIFO0) >0 )
{
return 1;
}
return 0;
}
void MyCAN_Receive(uint32_t *ID, uint8_t *Lenth, 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)//数据帧
{
for(uint8_t i=0; i<*Lenth; i++)
{
Data[i] = RxMessage.Data[i];
}
}
else
{
//遥控帧,暂时不处理
}
}