CAN详解(小白教程)

前言

从接触STM32到现在,我感觉CAN通讯可以说是我学过的最难的一个章节了,由于本人也是从小白开始,所以我觉得学习的时候我们可能有很多相似的疑惑。正所谓感同身受嘛,就是难难难难,希望我的经验可以帮助更多人。
这篇博客是我用课余时间写的,前前后后的花了三天时间,加上代码总共1.6W多字,整理不易。内容有点多,如果一次看不完可以点赞收藏
CAN详解(小白教程)_第1张图片

物理层

与I2C,SPI等同步通讯方式不同,CAN通讯是异步通讯,也就是没有时钟信号线来保持信号接收同步,也就是所说的半双工,无法同时发送与接收,在同一时刻,只能有一个节点发送数据,其余节点都只能接收数据。它有CAN_HIGH与CAN_LOW两条信号线组成。这两条线的组合方式也就造成了一下两种情况

1-闭环总线网络

CAN详解(小白教程)_第2张图片

2-闭环总线网络

这种形式的特点就是 高速 短距离 闭环 最高速度可达1Mbps,最长距离40m.
CAN详解(小白教程)_第3张图片
这种形式的特点就是 传输距离远,开环 最高速度125Kbps,最远距离1km.

以上这些了解就可以

3-通讯节点

这个概念还是要有的,看过我上篇贴子的应该知道在I2C协议中是存在主机与从机的,可以挂载多个从机,但是在CAN协议中,我们可以挂载多个 节点 ,通过总线来实现节点通讯,与其他协议不同的是,不对节点的地址进行编码,而是对节点的数据内容进行编码。理论上节点个数不受限制。

好了,说了这么多,那么节点是由什么构成的呢??不要急CAN详解(小白教程)_第4张图片
没错!如图所示,是由一个CAN控制器和一个CAN收发器组成滴~
问题又来了,怎么发送信号呢,I2C是逻辑信号。我简单说一个大概哈,一步一步来,看图说话
如果是发送数据:控制器发送一个信号(0或1),收发器将这个信号变成差分信号传送到总线中。
若果是接收数据:收发器将差分信号转化为0或1的二进制编码;

这时的你是不是有有点懵了,差分信号是什么????

--------------------------------PS:这是不是老母猪上树,一套又一套??----------------------------------------------
CAN详解(小白教程)_第5张图片

3-差分信号

CAN详解(小白教程)_第6张图片
差分差分,你品,是不是差,差分信号其实使用CAN_HIGH减去CAN_LOW的到的,在逻辑信号中,5V代表1, 0V代表0,而在差分信号中
0V---------逻辑1------隐形电平
2.0V-------逻辑0-----显性电平

-------------------------显性电平比隐形电平优先级要高,下面要用---------------------------------------------

是不是有点晕,好好捋捋奥
在这里插入图片描述
详情请参考这张表格
CAN详解(小白教程)_第7张图片

协议层

1-CAN协议的波特率与位同步

1– 由于CAN通讯协议并没有时钟信号线,所以各个节点之间要约定好特定的波特率进行通讯,特别的时候我们还需要使用位同步
2– 下面介绍一下位时序,所谓位时序,就是一个数据位的时序。我们可以把一个数据位分为四段
CAN详解(小白教程)_第8张图片
简单介绍一下这四段:
SS段: 又叫做同步段,它的作用就是判断节点与总线的时序是否一致,如何判断我们稍后再讲。先留一个疑点。它的长度为1tq,tq就相当于一个时间单位,我们可以规定它的大小,一般tq=1us.
PTS段: 传播时间段,用于补偿网络的延时时间
PBS1,PBS2: 都是用来补偿阶段的误差。

先了解一下,总之这几个段都是用来校验来确保数据传输准确的。

确定波特率

如图43-5所示,一个数据位有19tq,假设1tq=1us
波特率=1000000/19=52631.6bps

两种位同步方式

首先我们需要知道的是,当数据开始传输的时候会有一个帧起始信号,这个帧起始信号会产生一个下降沿(由高变低),正常情况下这个下降沿是在SS段的,如果不在就需要进行同步了

硬同步:

CAN详解(小白教程)_第9张图片
前面已经说过,下降沿要落在SS段,如图,出现这种情况,我们可以将SS段向左平移,让下降沿在SS段内。这种情况有一种限制,就是必须要存在帧起始信号。

重新同步:

CAN详解(小白教程)_第10张图片
这里就只介绍一种情况了,从前往后看,发现第一个下降沿在SS段之后,说明内部时序比总线时序要快2个Tq,所以我们可以在PBS1段增加两个时序,下个位时序就可以保持同步了。
同时这里定义了这个补偿时间叫做SJW,它的含义就是最大补偿值。一般保持在不大不小的状态,小了容易造成误差,大了影响传输速率。

2-CAN报文

可能有些同学第一次接触报文这个东西,这个我也没有特意去了解过,在我的理解里,报文就是通过特定方式对数据进行加密(包含各种特定的信息,比如ID,数据位之类的),然后接收的时候按照这种特定的方式进行解密
CAN详解(小白教程)_第11张图片
所谓的帧就是CAN报文了,接下来给大家介绍一下这几种帧(CAN报文)

帧种类

CAN详解(小白教程)_第12张图片

数据帧

CAN详解(小白教程)_第13张图片

标准数据帧

(1)帧起始:表示数据传输开始的意思,告诉一声数据要来了,他只有一个数据位,并且是显性电平,如果这里有点晕可以再去前面看看显性电平的定义,也就是逻辑0。
CAN详解(小白教程)_第14张图片

(2)仲裁段: 这段很重要,CAN通讯协议中不对节点地址分配优先级,而是对信息的重要程度分配优先级。仲裁段的主要内容就是ID信息。这个ID决定信息的优先级。对于重要的信息,我们可以给他一个高的优先级
CAN详解(小白教程)_第15张图片
当同时出现显性电平(0)和隐形电平(1)时,显性电平的优先级高,如图,此时的话,节点1报文就会失去对总线的占有权。
RTR段: 此段用于区分数据帧与远程帧,显性电平表示数据帧,隐形表示远程帧。
(3)控制段: r0,r1为保留位,默认显性,DLC段为数据长度。
(4)数据段: 存储着原始的数据,数据段中最重要的内容。IDE用于区分标准帧与扩展帧 显性为标准帧
(5)CRC段: 表示一个15位的校验码,算出来的校验码和接收到的校验码相同,表示正确,如果出现错位,则会通过错误帧返回,请求重新发送。
(6)CRC界定符: 分界线
(7)ACK段: 与I2C协议相似,表示应答。
(9)帧结束: 表示传输完成。

扩展数据帧

扩展数据帧与标准数据帧差别不算很大。区别如下
**仲裁段:**先是有11位ID,SSR段与RTR段相同,区分数据帧和远程帧,后面又有18位ID,扩展帧仲裁段的ID共有39位。其他大致相同

其他帧

这里就不多介绍了,数据帧是最麻烦的一种,搞懂了数据帧,其他帧也就手到擒来了
在这里插入图片描述
CAN详解(小白教程)_第16张图片
CAN详解(小白教程)_第17张图片

CAN外设

呼~写到这里真的很不容易,已经差不多6000字了!CAN外设这一块非常复杂,包括很多寄存器之类的,一些寄存器方面的细节我就不多说了,想仔细看一下寄存器的可以打开《stm32中文参考手册》,里面有很多寄存器。
CAN架构:
CAN详解(小白教程)_第18张图片

CAN主控制寄存器

1:
在这里插入图片描述在这里插入图片描述
CAN详解(小白教程)_第19张图片
CAN详解(小白教程)_第20张图片
四种工作模式:
CAN详解(小白教程)_第21张图片
正常模式:可以发送也可以接收
回环:自发自收,输出端可以发送数据到总线,总线可以检查数据。输入端只接受自己的数据。使用回环模式可以进行自检。
静默模式:逻辑0发不到总线,逻辑1 可以被发送到总线,它只能发送逻辑1。输入端可以从总线接收内容。;
回环静默模式:两种模式的结合,可以自发自收(不发送到总线),同时既不能发送到总线逻辑0与1;

----------------------怕你们懒,给搬运过来了,(其实是我懒)
CAN详解(小白教程)_第22张图片
这些模式自己看一下,到后面都是用库函数配置的,很方便。

2: 位时序以及波特率
CAN详解(小白教程)_第23张图片
STM32中CAN得位时序和之前讲的有点不一样,主要是PTS段和PBS1段合并成了BS1,BS2段就相当于PBS2段。那么他们的时间是怎么配置的呢?
BS1=Tq
(TS1[3:0]+1)
BS2=Tq
(TS2[3:0]+1)
Tq的配置如下,Tq=(BRP[9:0]+1)Tplck;
一个周期T=SS+BS1+B21=NTq;
波特率=1/NTq;
*
CAN详解(小白教程)_第24张图片

CAN发送邮箱

前面我们讲到CAN报文种类的时候说到数据帧里面包含很多段数据,这些数据就先存放在CAN的发送邮箱里。
CAN详解(小白教程)_第25张图片
需要强调一下的就是这个寄存器了CAN详解(小白教程)_第26张图片
仲裁段的ID,在标准模式下是11位的,也就是放在STID[10:0],扩展ID29位,当使用扩展ID的时候,就是放在EXID[0:28]。

FIFO

看到这可能就会有些懵了,FIFO是什么呢,其实它就是一个先进先出的数据缓存器,我们也可以理解为一个寄存器,CAN发送邮箱中的数据经过筛选器会转到这里来。
CAN详解(小白教程)_第27张图片

验收筛选器

CAN通讯不会对地址进行筛选,而是会在数据存放在FIFO前进行筛选,这个验收筛选器就起到了很重要的作用。
由两种分类方式,根据长度来区分:
在这里插入图片描述根据过滤方法来区分
CAN详解(小白教程)_第28张图片
这里重点说一下,所谓的标识符列表模式就好比我们把同意经过的所有ID列成一个表,如果来的ID与这个表中的某一个ID相同,那么就可以通过

而掩码模式就不一样了,他只是要求ID的某几位要一样,掩码中如果是1表示要和数据一样,0的话就表示随意,一样不一样都可以,我画一个表格来理解一下,x表示随意数据(0/1)
CAN详解(小白教程)_第29张图片

四种工作状态

CAN详解(小白教程)_第30张图片
CAN详解(小白教程)_第31张图片
一般我们用的话就用16位的就够了,看懂前面掩码标识符列表的看这里就应该明白了。

代码简介

CAN.C

#include "can.h"
#include "led.h"
#include "delay.h"
#include "usart.h"

//CAN³õʼ»¯
//tsjw:ÖØÐÂͬ²½ÌøԾʱ¼äµ¥Ôª.·¶Î§:CAN_SJW_1tq~ CAN_SJW_4tq
//tbs2:ʱ¼ä¶Î2µÄʱ¼äµ¥Ôª.   ·¶Î§:CAN_BS2_1tq~CAN_BS2_8tq;
//tbs1:ʱ¼ä¶Î1µÄʱ¼äµ¥Ôª.   ·¶Î§:CAN_BS1_1tq ~CAN_BS1_16tq
//brp :²¨ÌØÂÊ·ÖƵÆ÷.·¶Î§:1~1024;  tq=(brp)*tpclk1
//²¨ÌØÂÊ=Fpclk1/((tbs1+1+tbs2+1+1)*brp);
//mode:CAN_Mode_Normal,ÆÕͨģʽ;CAN_Mode_LoopBack,»Ø»·Ä£Ê½;
//Fpclk1µÄʱÖÓÔÚ³õʼ»¯µÄʱºòÉèÖÃΪ36M,Èç¹ûÉèÖÃCAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
//Ôò²¨ÌØÂÊΪ:36M/((8+9+1)*4)=500Kbps
//·µ»ØÖµ:0,³õʼ»¯OK;
//    ÆäËû,³õʼ»¯Ê§°Ü;


u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{

	  GPIO_InitTypeDef GPIO_InitStructure; 
	  CAN_InitTypeDef        CAN_InitStructure;
 	  CAN_FilterInitTypeDef  CAN_FilterInitStructure;
#if CAN_RX0_INT_ENABLE 
   	NVIC_InitTypeDef  NVIC_InitStructure;
#endif

	  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//ʹÄÜPORTAʱÖÓ	                   											 

  	RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//ʹÄÜCAN1ʱÖÓ	

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//¸´ÓÃÍÆÍì
    GPIO_Init(GPIOA, &GPIO_InitStructure);		//³õʼ»¯IO
   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//ÉÏÀ­ÊäÈë
    GPIO_Init(GPIOA, &GPIO_InitStructure);//³õʼ»¯IO
	  
 	//CANµ¥ÔªÉèÖÃ
 	  CAN_InitStructure.CAN_TTCM=DISABLE;						 //·Çʱ¼ä´¥·¢Í¨ÐÅģʽ  //
 	  CAN_InitStructure.CAN_ABOM=DISABLE;						 //Èí¼þ×Ô¶¯ÀëÏß¹ÜÀí	 //
  	CAN_InitStructure.CAN_AWUM=DISABLE;						 //˯Ãßģʽͨ¹ýÈí¼þ»½ÐÑ(Çå³ýCAN->MCRµÄSLEEPλ)//
  	CAN_InitStructure.CAN_NART=ENABLE;						 	//½ûÖ¹±¨ÎÄ×Ô¶¯´«ËÍ //
  	CAN_InitStructure.CAN_RFLM=DISABLE;						 //±¨ÎIJ»Ëø¶¨,еĸ²¸Ç¾ÉµÄ // 
  	CAN_InitStructure.CAN_TXFP=DISABLE;						 //ÓÅÏȼ¶Óɱ¨Îıêʶ·û¾ö¶¨ //
  	CAN_InitStructure.CAN_Mode= mode;	         //ģʽÉèÖ㺠mode:0,ÆÕͨģʽ;1,»Ø»·Ä£Ê½; //
  	//ÉèÖò¨ÌØÂÊ
  	CAN_InitStructure.CAN_SJW=tsjw;				//ÖØÐÂͬ²½ÌøÔ¾¿í¶È(Tsjw)Ϊtsjw+1¸öʱ¼äµ¥Î»  CAN_SJW_1tq	 CAN_SJW_2tq CAN_SJW_3tq CAN_SJW_4tq
  	CAN_InitStructure.CAN_BS1=tbs1; //Tbs1=tbs1+1¸öʱ¼äµ¥Î»CAN_BS1_1tq ~CAN_BS1_16tq
  	CAN_InitStructure.CAN_BS2=tbs2;//Tbs2=tbs2+1¸öʱ¼äµ¥Î»CAN_BS2_1tq ~	CAN_BS2_8tq
  	CAN_InitStructure.CAN_Prescaler=brp;            //·ÖƵϵÊý(Fdiv)Ϊbrp+1	//
  	CAN_Init(CAN1, &CAN_InitStructure);            // ³õʼ»¯CAN1 

 	  CAN_FilterInitStructure.CAN_FilterNumber=0;	  //¹ýÂËÆ÷0
   	CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; 
  	CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32λ 
  	CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;////32λID
  	CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
  	CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32λMASK
  	CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
  	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//¹ýÂËÆ÷0¹ØÁªµ½FIFO0
 	  CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //¼¤»î¹ýÂËÆ÷0

  	CAN_FilterInit(&CAN_FilterInitStructure);//Â˲¨Æ÷³õʼ»¯
#if CAN_RX0_INT_ENABLE
	
	  CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);//FIFO0ÏûÏ¢¹ÒºÅÖжÏÔÊÐí.		    
  
  	NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     // Ö÷ÓÅÏȼ¶Îª1
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            // ´ÎÓÅÏȼ¶Îª0
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  	NVIC_Init(&NVIC_InitStructure);
#endif
	return 0;
}   
 
#if CAN_RX0_INT_ENABLE	//ʹÄÜRX0ÖжÏ
//ÖжϷþÎñº¯Êý			    
void USB_LP_CAN1_RX0_IRQHandler(void)
{
  	CanRxMsg RxMessage;
	int i=0;
    CAN_Receive(CAN1, 0, &RxMessage);
	for(i=0;i<8;i++)
	printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);
}
#endif

//can·¢ËÍÒ»×éÊý¾Ý(¹Ì¶¨¸ñʽ:IDΪ0X12,±ê×¼Ö¡,Êý¾ÝÖ¡)	
//len:Êý¾Ý³¤¶È(×î´óΪ8)				     
//msg:Êý¾ÝÖ¸Õë,×î´óΪ8¸ö×Ö½Ú.
//·µ»ØÖµ:0,³É¹¦;
//		 ÆäËû,ʧ°Ü;
u8 Can_Send_Msg(u8* msg,u8 len)
{	
  u8 mbox;
  u16 i=0;
  CanTxMsg TxMessage;
  TxMessage.StdId=0x12;					 // ±ê×¼±êʶ·û 
  TxMessage.ExtId=0x12;				   // ÉèÖÃÀ©Õ¹±êʾ·û 
  TxMessage.IDE=CAN_Id_Standard; // ±ê×¼Ö¡
  TxMessage.RTR=CAN_RTR_Data;		 // Êý¾ÝÖ¡
  TxMessage.DLC=len;						// Òª·¢Ë͵ÄÊý¾Ý³¤¶È
  for(i=0;i=0XFFF)return 1;
  return 0;		

}
//can¿Ú½ÓÊÕÊý¾Ý²éѯ
//buf:Êý¾Ý»º´æÇø;	 
//·µ»ØÖµ:0,ÎÞÊý¾Ý±»ÊÕµ½;
//		 ÆäËû,½ÓÊÕµÄÊý¾Ý³¤¶È;
u8 Can_Receive_Msg(u8 *buf)
{		   		   
 	u32 i;
	CanRxMsg RxMessage;
    if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;		//ûÓнÓÊÕµ½Êý¾Ý,Ö±½ÓÍ˳ö 
    CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//¶ÁÈ¡Êý¾Ý	
    for(i=0;i<8;i++)
    buf[i]=RxMessage.Data[i];  
	return RxMessage.DLC;	
}



在这里插入图片描述
可以通过改变这个数来改变接受的ID

步骤:
配置GPIO
设置工作模式
确定波特率
配置筛选器
配置接受中断(FIFO接收到数据时会产生一个中断)
发送函数配置
接受函数配置

main.c

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"	 
#include "can.h" 
 
 
/************************************************
 ALIENTEK¾«Ó¢STM32¿ª·¢°åʵÑé25
 CAN ʵÑé   
 ¼¼ÊõÖ§³Ö£ºwww.openedv.com
 ÌÔ±¦µêÆÌ£ºhttp://eboard.taobao.com 
 ¹Øע΢ÐŹ«ÖÚƽ̨΢Ðźţº"ÕýµãÔ­×Ó"£¬Ãâ·Ñ»ñÈ¡STM32×ÊÁÏ¡£
 ¹ãÖÝÊÐÐÇÒíµç×ӿƼ¼ÓÐÏÞ¹«Ë¾  
 ×÷ÕߣºÕýµãÔ­×Ó @ALIENTEK
************************************************/
 


 int main(void)
 {	 
	u8 key;
	u8 i=0,t=0;
	u8 cnt=0;
	u8 canbuf[8];
	u8 res;
	u8 mode=CAN_Mode_LoopBack;//CAN¹¤×÷ģʽ;CAN_Mode_Normal(0)£ºÆÕͨģʽ£¬CAN_Mode_LoopBack(1)£º»·»Øģʽ

	delay_init();	    	 //ÑÓʱº¯Êý³õʼ»¯	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//ÉèÖÃÖжÏÓÅÏȼ¶·Ö×éΪ×é2£º2λÇÀÕ¼ÓÅÏȼ¶£¬2λÏìÓ¦ÓÅÏȼ¶
	uart_init(115200);	 	//´®¿Ú³õʼ»¯Îª115200
	LED_Init();		  		//³õʼ»¯ÓëLEDÁ¬½ÓµÄÓ²¼þ½Ó¿Ú
	LCD_Init();			   	//³õʼ»¯LCD	
	KEY_Init();				//°´¼ü³õʼ»¯		 	
   
	CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);//CAN³õʼ»¯»·»Øģʽ,²¨ÌØÂÊ500Kbps    

 	POINT_COLOR=RED;//ÉèÖÃ×ÖÌåΪºìÉ« 
	LCD_ShowString(60,50,200,16,16,"WarShip STM32");	
	LCD_ShowString(60,70,200,16,16,"CAN TEST");	
	LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(60,110,200,16,16,"2015/1/11");
	LCD_ShowString(60,130,200,16,16,"LoopBack Mode");	 
	LCD_ShowString(60,150,200,16,16,"KEY0:Send WK_UP:Mode");//ÏÔʾÌáʾÐÅÏ¢		
  POINT_COLOR=BLUE;//ÉèÖÃ×ÖÌåΪÀ¶É«	  
	LCD_ShowString(60,170,200,16,16,"Count:");			//ÏÔʾµ±Ç°¼ÆÊýÖµ	
	LCD_ShowString(60,190,200,16,16,"Send Data:");		//Ìáʾ·¢Ë͵ÄÊý¾Ý	
	LCD_ShowString(60,250,200,16,16,"Receive Data:");	//Ìáʾ½ÓÊÕµ½µÄÊý¾Ý		
 	while(1)
	{
		key=KEY_Scan(0);
		if(key==KEY0_PRES)//KEY0°´ÏÂ,·¢ËÍÒ»´ÎÊý¾Ý
		{
			for(i=0;i<8;i++)
			{
				canbuf[i]=cnt+i;//Ìî³ä·¢ËÍ»º³åÇø
				if(i<4)LCD_ShowxNum(60+i*32,210,canbuf[i],3,16,0X80);	//ÏÔʾÊý¾Ý
				else LCD_ShowxNum(60+(i-4)*32,230,canbuf[i],3,16,0X80);	//ÏÔʾÊý¾Ý
 			}
			res=Can_Send_Msg(canbuf,8);//·¢ËÍ8¸ö×Ö½Ú 
			if(res)LCD_ShowString(60+80,190,200,16,16,"Failed");		//Ìáʾ·¢ËÍʧ°Ü
			else LCD_ShowString(60+80,190,200,16,16,"OK    ");	 		//Ìáʾ·¢Ëͳɹ¦								   
		}else if(key==WKUP_PRES)//WK_UP°´Ï£¬¸Ä±äCANµÄ¹¤×÷ģʽ
		{	   
			mode=!mode;
  			CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);//CANÆÕͨģʽ³õʼ»¯, ²¨ÌØÂÊ500Kbps 
			POINT_COLOR=RED;//ÉèÖÃ×ÖÌåΪºìÉ« 
			if(mode==0)//ÆÕͨģʽ£¬ÐèÒª2¸ö¿ª·¢°å
			{
				LCD_ShowString(60,130,200,16,16,"Nnormal Mode ");	    
			}else //»Ø»·Ä£Ê½,Ò»¸ö¿ª·¢°å¾Í¿ÉÒÔ²âÊÔÁË.
			{
 				LCD_ShowString(60,130,200,16,16,"LoopBack Mode");
			}
 			POINT_COLOR=BLUE;//ÉèÖÃ×ÖÌåΪÀ¶É« 
		}		 
		key=Can_Receive_Msg(canbuf);
		if(key)//½ÓÊÕµ½ÓÐÊý¾Ý
		{			
			LCD_Fill(60,270,130,310,WHITE);//Çå³ý֮ǰµÄÏÔʾ
 			for(i=0;i

------------------------因为贫穷,莫得两块板子,所以只能用环回模式了------------------------------
CAN详解(小白教程)_第32张图片
效果:当按下key0时,lcd显示屏上会有发送和接受到的数据

总结

后面的代码部分因为时间匆忙就没怎么讲,不过我自认为前面还是很细的(自夸一波),不过一些寄存器,以及代码里的结构体我都没有多说,看完前面部分再来看代码应该很容易的,不过需要你自己再去看一下文档。
接下来计划会向前更新一些关于中断,定时器的知识。

你可能感兴趣的:(CAN详解(小白教程))