打印
[学习资料]

基于dsPIC33FJ128MC506的CAN通信(转)

[复制链接]
1423|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Sode|  楼主 | 2020-2-21 17:31 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
基于dsPIC33FJ128MC506的CAN通信



              系统硬件

  利用TJA1054作为CAN收发器,dsPIC33FJ128MC506 CAN通讯要注意的是,在处理器和收发器之间要加光耦以隔离两者之间的电源。原理图如图1所示。


  系统初始化

  Microchip公司的dspPIC33FJ128MC506芯片中ECAN通信模块的初始化分为五个部分:系统工作时钟设置、ECAN接收和发射缓冲区的设置、ECAN波特率的设置、接收过滤寄存器和屏蔽寄存器的设置以及ECAN收发器TJA1054的启动。


图1 dsPIC33FJ128MC506 CAN通讯原理图


  系统工作时钟设置

  dsPIC33FJ128MC506可以选择多种外部和内部振荡器作为时钟源,并通过时钟控制寄存器OSCCON进行设置。对时钟的选择要在系统的配置存储区(0x800000-0xFFFFFF)进行,在程序中一般通过几句宏指令语句指定。

  以下语句选择时钟并禁能看门狗,其他的功能请参看芯片说明书。

  _FOSCSEL(FNOSC_PRIPLL); //选用带PLL的主振荡器

  _FOSC(POSCMD_HS); // 主振荡器为HS型晶振

  _FWDT(FWDTEN_OFF); // 看门狗禁能

  下面的函数对系统时钟进行配置, 外部晶振为8MHz,系统工作时钟为40MHz。

  void oscConfig(void)

  {

  CLKDIVbits.PLLPRE = 0; //外设时钟2分频为4M

  PLLFBDbits.PLLDIV = 18; //20倍频,为80M

  CLKDIVbits.PLLPOST = 0; //外设时钟2分频为40M

  while(OSCCONbits.LOCK!=1){ }; //等待设置生效

  }

  ECAN接收和发射缓冲区的设置

  DMA(直接存储器访问)方式是外设SFR与RAM间进行数据复制的非常高效的机制,dsPIC33FJ128MC506的ECAN模块支持DMA传输,共有8个DMA通道可供选择。在此我们选用0和2通道分别作为ECAN1的发射和接收。初始化语句如下:

  DMA0通道初始化为ECAN1发射:

  void dma0init(void)

  {

  DMACS0=0; //清DMA控制器状态位

  DMA0CON=0x2020; //DMA为外设间接寻址模式,支持从DMA读,并写到外设

  DMA0PAD=0x0442; //DMA0PAD下载为ECAN1发射寄存器的地址

  DMA0CNT=0x0007; //传输计数寄存器为8

  DMA0REQ=0x0046; //DMA外设REQ编号选择位

  DMA0STA= __builtin_dmaoffset(ecan1msgBuf); //DMA起始地址位

  DMA0CONbits.CHEN=1;

  }

  DMA2通道初始化为ECAN1接收:

  void dma2init(void)

  {

  DMACS0=0; //清DMA控制器状态位

  DMA2CON=0x0020; //DM为外设间接寻址模式,支持从DMA写,并读到外设

  DMA2PAD=0x0440; //DMA0PAD下载为ECAN1接收寄存器的地址

  DMA2CNT=0x0007; //传输计数寄存器为8

  DMA2REQ=0x0022; //DMA外设REQ编号选择位

  DMA2STA= __builtin_dmaoffset(&ecan1msgBuf[2][0]); //DMA起始地址位

  DMA2CONbits.CHEN=1;

  }

  上面程序中的“DMA0STA= __builtin_dmaoffset(ecan1msgBuf);”和“ DMA2STA= __builtin_dmaoffset(&ecan1msgBuf[2][0]); ”分别指明了DMA的起始地址位为ecan1msgBuf和(&ecan1msgBuf[2][0],ecan1msgBuf是一个两维数组,在相关头文件中定义,其语句为:

  ECAN1缓冲器的设置:

  #define ECAN1_MSG_BUF_LENGTH 4 //长度为4个字

  //缓冲区为二维数据

  typedef unsigned int ECAN1MSGBUF [ECAN1_MSG_BUF_LENGTH][8];

  //数组位于DMA空间

  extern ECAN1MSGBUF ecan1msgBuf __attribute__((space(dma)));

  波特率的设置

  正确设置通信波特率必须配置以下几个参数:同步跳转宽度、波特率预分频比、相位段1和相位段2的长度、采样次数及传播时间段的长度。设置程序语句如下:


  voide CAN1ClkInit(void)

  {

  /*指定 CAN通信时钟利用系统的指令周期,在此为20MHz,即Fcan=20MHz*/

  C1CTRL1bits.CANCKS = 1;

  /*规定一个CAN位包含16个TQ,分配如下:*/

  C1CFG1bits.SJW = 3; //同步段 = 1TQ

  C1CFG2bits.SEG1PH=3; //相位传输段1 = 4TQ

  C1CFG2bits.SEG2PHTS = 1; //相位传输段2长度可编程设定

  C1CFG2bits.SEG2PH = 3; //相位传输段2 = 4TQ

  C1CFG2bits.PRSEG = 6; //传播时间段 = 7TQ

  C1CFG2bits.SAM = 0; //采样次数为1次

  /*根据上面设置,算出波特率的分频比。这里要注意的是,因为C1CFG1bits.BRP只能填入整数,那么在系统时钟、通讯速率和时间份额三者之间要合理选择,否则通信不会成功。例如:如果系统时钟选用其内部FRC,标称值为7.37M,倍频后的系统时钟为36.85M,采用16个TQ为一个 CAN位,假设CAN的通信速率为125K的话,那么根据公式BRP=Fcan/(2*16*125K)-1,计算得出的值为3.6,因为不能整除,所以永远不能得到125K的通信速率。在此我们取40M的时钟,指令周期为20M,一个CAN位为16个TQ,经计算可得BRP的值为4。*/

  C1CFG1bits.BRP=4;

  }

  接收过滤寄存器和屏蔽寄存器的设置

  /* 下面的函数用来设置接收过滤器"n" ,各输入参数的意义分别为:

  n-> 过滤器号,范围为[0-15]

  identifier-> 合法的标识符

  exide -> 是否扩展数据帧,"0" 表示为标准数据帧,"1"表示为扩展数据帧

  bufPnt -> 过滤后的信息存放的缓冲区,范围为[0-15]

  maskSel -> 关连的屏蔽寄存器[0-3] */

  void ecan1WriteRxAcptFilter(int n, long identifier, unsigned int exide, unsigned int bufPnt, unsigned int maskSel)

  {

  /*定义局部变量*/

  unsigned long sid10_0=0, eid15_0=0, eid17_16=0;

  unsigned int *sidRegAddr,*bufPntRegAddr,*maskSelRegAddr, *fltEnRegAddr;

  /*因为将要设置的特殊寄存器要与别的寄存器共用地址,所以需设置控制位WIN*/

  C1CTRL1bits.WIN=1;

  /* 根据输入的参数,计算出相应寄存器的地址,包括滤波器n的地址、缓冲指针寄存器地址、相关的屏蔽寄存器的地址及接收过滤使能寄存器的相关位*/

  // 接过过滤寄存器的地址

  sidRegAddr = (unsigned int *)(&C1RXF0SID + (n << 1));

  //报文缓冲区地址

  bufPntRegAddr = (unsigned int *)(&C1BUFPNT1 + ((n >> 2)*2) ) ;

  //屏蔽寄存器地址

  maskSelRegAddr = (unsigned int *)(&C1FMSKSEL1 + ((n >> 3)*2));

  //过滤器使能地址

  fltEnRegAddr = (unsigned int *)(&C1FEN1);

  /* 将ID按规定分配到相关的寄存器中*/

  if(exide==1) { //扩展帧的ID

  eid15_0 = (identifier & 0xFFFF);

  eid17_16= (identifier>>16) & 0x3;

  sid10_0 = (identifier>>18) & 0x7FF;

  *sidRegAddr=(sid10_0)<<5 + 0x8 + eid17_16;

  *(sidRegAddr+2)= eid15_0;

  }else{ //标准帧的ID

  sid10_0 = (identifier & 0x7FF);


  *sidRegAddr=(sid10_0)<<5;

  *(sidRegAddr+2)=0;

  }

  *bufPntRegAddr = (bufPnt << (4*(n&3))); // 写缓冲指针寄存器CiBUFPNTn内容*maskSelRegAddr = (maskSel << (2*(n&7))); // 确定关连屏蔽寄存器

  CiFMSKSELn *fltEnRegAddr = (0x1 << n); //使能第n个滤波器

  C1CTRL1bits.WIN=0; //恢复寄存器地址选择位

  }

  /*

  下面的函数用来写接收屏蔽寄存器"m" ,各输入参数的意义分别为:

  m-> 屏蔽寄存器号[0-3]

  identifier->屏蔽位

  mide -> "0" 表示无论是标准帧还是扩展帧,屏蔽器都起作用

  "1" 表示屏蔽器是否起作用要参照’exide’ 位 */

  void eCAN1WriteRxAcptMask(int m, long identifier, unsigned int mide)

  {

  /*定义局部变量*/

  unsigned long sid10_0=0, eid15_0=0, eid17_16=0;

  unsigned int *maskRegAddr;

  //因为将要设置的特殊寄存器要与别的寄存器共用地址,所以需设置控制位WIN

  C1CTRL1bits.WIN=1;

  /* 根据"m"计算出CiRXMmSID 寄存器的地址*/

  maskRegAddr = (unsigned int *)(&C1RXM0SID + (m << 2));

  /* 将屏敝ID写入到屏蔽寄存器中*/

  if(mide==1) { //扩展帧格式

  eid15_0 = (identifier & 0xFFFF);

  eid17_16= (identifier>>16) & 0x3;

  sid10_0 = (identifier>>18) & 0x7FF;

  *maskRegAddr=(sid10_0)<<5 + 0x8 + eid17_16;

  *(maskRegAddr+2)= eid15_0;

  }else{ // 标准帧格式

  sid10_0 = (identifier & 0x7FF);

  *maskRegAddr=(sid10_0)<<5; *(maskRegAddr+2)=0;

  }

  C1CTRL1bits.WIN=0;

  }

  TJA1054的启动

  TJA1054是广泛应用的低速容错CAN收发器,其工作启动要按照其说明进行,下面的函数将TJA1054初始化为工作状态。

  void TJA1054Init (void)

  {  

  TRISBbits.TRISB15=1; //将错误引脚设为输入状态

  TRISEbits.TRISE4=0; //将EN引脚设为输出状态

  TRISEbits.TRISE5=0; //将STB引脚设为输出状态

  PORTEbits.RE5=1; //STB=0;

  PORTEbits.RE4=0; //EN=1

  PORTEbits.RE5=1; //STB=1;

  PORTEbits.RE4=1; //EN=1

  }

  结合以上内容,ECAN1的初始化函数为:

  void ecan1Init(void)

  {

  C1CTRL1bits.REQOP=4;

  while(C1CTRL1bits.OPMODE!=4); //请求进入配置模式

  ecan1ClkInit(); //调用波特率设置函数

  C1FCTRLbits.DMABS=0b000; //在DMA RAM 中设置4个CAN报文缓冲

  ecan1WriteRxAcptFilter(1,0x36,0,2,0); //配置接过收滤寄存器

  /*配置接收屏蔽寄存器,要注意的是,这里设为“1”的位是非屏蔽位,设为“0”的位为屏蔽位。这里的0x7FF将会对标准帧ID的每一位都进行检查是否对应的过滤器相匹配*/

  ecan1WriteRxAcptMask(0,0x7ff,0);

  C1CTRL1bits.REQOP=0;

  while(C1CTRL1bits.OPMODE!=0); //进入正常模式

  /*以下语句设置CAN报文的发射接收控制*/

  C1RXFUL1=C1RXFUL2=C1RXOVF1=C1RXOVF2=0x0000;

  C1TR01CONbits.TXEN0=1; /*ECAN1的0缓冲器为发射缓冲*/

  C1TR01CONbits.TXEN1=0; /* ECAN1的1缓冲器为接收缓冲 */

  C1TR01CONbits.TX0PRI=0b11; /* 发射缓冲的优先级*/

  }

  数据的发射和接收

  写报文ID

  下面的函数将一个报文ID写到ECAN1的发射缓冲区,其中各个参数代表的含义如下:

  /*buf -> 发射寄存器号

  txIdentifier ->发射报文的ID

  ide -> "0" 报文为标准帧

  "1" 报文为扩展帧

  remoteTransmit -> "0" 报文为正常报文

  "1" 报文为远程报文 */

  void ecan1WriteTxMsgBufId(unsigned int buf, long txIdentifier, unsigned int ide, unsigned int remoteTransmit)

  {

  /*定义局部变量*/

  unsigned long word0=0, word1=0, word2=0;

  unsigned long sid10_0=0, eid5_0=0, eid17_6=0;

  /*赋值*/

  eid5_0 = (txIdentifier & 0x7FF);

  eid17_6 = (txIdentifier>>6) & 0x7F;

  sid10_0 = (txIdentifier>>18) & 0x7FF;

  word1 = eid17_6;

  if(remoteTransmit==1) // 远程帧

  {

  word0 = ((sid10_0 << 2) | ide | 0x2);

  word2 = ((eid5_0 << 10)| 0x0200);

  }

  else { //正常帧

  word2 = 0;

  word0 = (eid5_0 << 2);

  }

  /*将地址写入发射缓冲器对应的区域 */

  ecan1msgBuf[buf][0] = word0;

  ecan1msgBuf[buf][1] = word1;

  ecan1msgBuf[buf][2] = word2;

  }

  写报文内容

  /*下面的函数将待发报文内容写入到发射缓冲区,各参数含义如下:

  buf -> 发射缓冲器号

  dataLength -> 数据的长度

  data1/data2/data3/data4 -> 发射数据内容*/

  void ecan1WriteTxMsgBufData(unsigned int buf, unsigned int dataLength, unsigned int data1, unsigned int data2, unsigned int data3, unsigned int data4)

  {

  ecan1msgBuf[buf][2] = ((ecan1msgBuf[buf][2] & 0xFFF0) + dataLength) ;//数据长度

  ecan1msgBuf[buf][3] = data1; //数据1

  ecan1msgBuf[buf][4] = data2; //数据2

  ecan1msgBuf[buf][5] = data3; //数据3

  ecan1msgBuf[buf][6] = data4; //数据4

  }

  报文的接收

  报文接收在中断中进行,函数语句如下:

  void __attribute__((interrupt, no_auto_psv))_C1Interrupt(void) //CAN1中断

  {

  IFS2bits.C1IF = 0; //请中断标志

  if(C1INTFbits.TBIF) //是否为发射中断

  {

  C1INTFbits.TBIF = 0;

  }

  if(C1INTFbits.RBIF) //是否为接收中断

  {

  C1INTFbits.RBIF = 0; //请接收中断标志位

  /*读出报文*/

  ReceiveMessageID = (eCAN1msgBuf[2][0]>>2);  


  ReveiveDataLength = (ecan1msgBuf[2][2]&0x000f);

  ReceiveData[0] = ecan1msgBuf[2][3];

  ReceiveData[1] = ecan1msgBuf[2][4];

  ReceiveData[2] = ecan1msgBuf[2][5];

  ReceiveData[3] = ecan1msgBuf[2][6];

  C1RXFUL1bits.RXFUL2 = 0; //清报文溢出标志

  ReceiveNewMessage = 1; //接收标志置位

  }

  C1RXOVF1bits.RXOVF2 = 0;

  }

  写标准报文函数

  void ecan1WriteMessage(unsigned int ID,unsigned int Length,unsigned int *Data)

  {

  ecan1WriteTxMsgBufId(0,ID,0,0);

  ecan1WriteTxMsgBufData(0,Length,*Data,*(Data+1),*(Data+2),*(Data+3));

  C1TR01CONbits.TXREQ0=1;

  }

  CAN通信示例程序

  用于检验ECAN1的发射接收,其功能是收到CANoe发送COMMAND_BSI后,开始发送数据。

  int main(void)

  {

  oscConfig() //振荡器设置

  /* 清中断各标志位*/

  IFS0=0;

  IFS1=0;

  IFS2=0;

  IFS3=0;

  IFS4=0;

  ecan1Init(); //初始化ECAN1

  dma0init(); //DMA初始化

  dma2init();

  TJA1054Init(); //TJA1054初始化

  /*使能ECAN1中断*/

  IEC2bits.C1IE = 1;

  C1INTEbits.TBIE = 1;

  C1INTEbits.RBIE = 1;

  while (1)

  {

  /*向ECAN1发射缓冲区写报文并请求发射*/

  can1WriteMessage(TXMmessageID, TXMessageLength, TXMessage);

  }

  }

  CANoe测试结果如图2所示。


图2 CANoe通讯测试界面



使用特权

评论回复
沙发
1399866558| | 2020-2-21 20:26 | 只看该作者
MARK一下

使用特权

评论回复
板凳
東南博士| | 2020-2-22 09:20 | 只看该作者
dspPIC33FJ128MC506 标记一下!CAN转发器

使用特权

评论回复
地板
wangjiahao88| | 2020-2-22 09:22 | 只看该作者
dspPIC33FJ128MC506 标记一下!CAN 应该是收发器,而不是转发器。。。

使用特权

评论回复
5
zhuomuniao110| | 2020-2-23 19:55 | 只看该作者
多谢分享,寄存器操作的,我现在都看的頭da

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1049

主题

1522

帖子

8

粉丝