历史上的今天
返回首页

历史上的今天

今天是:2024年09月02日(星期一)

正在发生

2018年09月02日 | 使用STM32F103做CAN的收发通信

2018-09-02 来源:eefocus

一、can通信

CAN 是Controller Area Network 的缩写(以下称为CAN),是ISO国际标准化的串行通信协议。 

CAN协议是通过以下5种类型的帧进行的: 
l 数据帧 
l 摇控帧 
l 错误帧 
l 过载帧 
l 帧间隔 
另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11 个位的标识符(ID),扩展格式有29 个位的ID。

大部分系统使用的都是数据帧 ,我这里使用的也是数据帧。 
数据帧一般由7个段构成,即: 
(1) 帧起始。表示数据帧开始的段。 
(2) 仲裁段。表示该帧优先级的段。 
(3) 控制段。表示数据的字节数及保留位的段。 
(4) 数据段。数据的内容,一帧可发送0~8个字节的数据。 
(5) CRC段。检查帧的传输错误的段。 
(6) ACK段。表示确认正常接收的段。 
(7) 帧结束。表示数据帧结束的段。

明确了数据帧概念,还需要理解一下过滤器的作用。

STM32的标识符屏蔽滤波目的是减少了CPU处理CAN通信的开销。STM32的过滤器组最多有28个(互联型),但是STM32F103ZET6只有14个(增强型),每个滤波器组x由2个32为寄存器,CAN_FxR1和CAN_FxR2组成。 
STM32每个过滤器组的位宽都可以独立配置,以满足应用程序的不同需求。根据位宽的不同,每个过滤器组可提供: 
● 1个32位过滤器,包括:STDID[10:0]、EXTID[17:0]、IDE和RTR位 
● 2个16位过滤器,包括:STDID[10:0]、IDE、RTR和EXTID[17:15]位 
此外过滤器可配置为,屏蔽位模式和标识符列表模式。 
在屏蔽位模式下,标识符寄存器和屏蔽寄存器一起,指定报文标识符的任何一位,应该按照“必须匹配”或“不用关心”处理。 
而在标识符列表模式下,屏蔽寄存器也被当作标识符寄存器用。因此,不是采用一个标识符加一个屏蔽位的方式,而是使用2个标识符寄存器。接收报文标识符的每一位都必须跟过滤器标识符相同。

一般也都是使用标识符列表模式,这里使用的也是标识符列表模式。滤波过程举例如下: 
这里写图片描述

在程序中就是:

//要过滤的ID高位 

CAN_FilterInitStructure.CAN_FilterIdHigh=0X00;  

//要过滤的ID低位                 

CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; 


//过滤器屏蔽标识符的高16位值

CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0xFFFF;   

//过滤器屏蔽标识符的低16位值         

CAN_FilterInitStructure.CAN_FilterMaskIdLow=0xFFFF;             


这里的CAN_FilterId和CAN_FilterMaskId是配合使用的,意思是CAN_FilterId指出需要屏蔽ID的什么内容,什么格式;CAN_FilterMaskId是指CAN_FilterId的每一位是否需要过滤,若CAN_FilterMaskId在某位上是1的话,ID对应位上的数值就必须和CAN_FilterId该位上的一样,保持一致,反之则是“不关心”。


上述程序的设置的含义就是:只接收来自0x1314的数据,屏蔽其他ID的数据。


二、程序思路


这里准备做一个主机与从机的通信,主要用扩展标识符ExtId来区分,分配的标识符是: 

主机:0x1314 

从机:0x1311


主机负责接收所有从机的数据,不需要过滤,用扩展标识符ExtId来区分不同从机的数据;主机还可以向不同从机发送信息。而从机则只接收来自主机的数据,同样用扩展标识符ExtId来区分是否是发向自己的数据;同时,也能够向主机发送信息。


三、相关代码


代码也是非常简单的,这里贴出了主机和从机的can.c和can.h两个文件。


从机相关代码


can.c文件


#include "can.h"


/* 在中断处理函数中返回 */

//__IO uint32_t ret = 0;


//接收数据缓冲器

u8 RxBuf[5];

u8 Rx_flag=0;


void CAN1_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure; 

    NVIC_InitTypeDef NVIC_InitStructure;

    CAN_InitTypeDef        CAN_InitStructure;

    CAN_FilterInitTypeDef  CAN_FilterInitStructure;


    /* 复用功能和GPIOB端口时钟使能*/    

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE);                                                                      


    /* CAN1 模块时钟使能 */

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); 


    /* Configure CAN pin: RX */  // PB8

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;       //上拉输入

    GPIO_Init(GPIOB, &GPIO_InitStructure);


    /* Configure CAN pin: TX */   // PB9

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     //复用推挽输出

    GPIO_Init(GPIOB, &GPIO_InitStructure);


    //#define GPIO_Remap_CAN    GPIO_Remap1_CAN1 本实验没有用到重映射I/O

    GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);


    //CAN_NVIC_Configuration(); //CAN中断初始化   

    /* Configure the NVIC Preemption Priority Bits */  

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);


    #ifdef  VECT_TAB_RAM  

      /* Set the Vector Table base location at 0x20000000 */ 

      NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); 

    #else  /* VECT_TAB_FLASH  */

      /* Set the Vector Table base location at 0x08000000 */ 

      NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);   

    #endif


    /* enabling interrupt */

    NVIC_InitStructure.NVIC_IRQChannel=USB_LP_CAN1_RX0_IRQn;;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);


    //CAN_INIT();//CA初始化N模块 

    /* CAN register init */

    CAN_DeInit(CAN1);                       //将外设CAN的全部寄存器重设为缺省值

    CAN_StructInit(&CAN_InitStructure);     //把CAN_InitStruct中的每一个参数按缺省值填入


    /* CAN cell init */

    CAN_InitStructure.CAN_TTCM=DISABLE;         //没有使能时间触发模式

    CAN_InitStructure.CAN_ABOM=DISABLE;         //没有使能自动离线管理

    CAN_InitStructure.CAN_AWUM=DISABLE;         //没有使能自动唤醒模式

    CAN_InitStructure.CAN_NART=DISABLE;         //没有使能非自动重传模式

    CAN_InitStructure.CAN_RFLM=DISABLE;         //没有使能接收FIFO锁定模式

    CAN_InitStructure.CAN_TXFP=DISABLE;         //没有使能发送FIFO优先级

    CAN_InitStructure.CAN_Mode=CAN_Mode_Normal; //CAN设置为正常模式

    CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;      //重新同步跳跃宽度1个时间单位

    CAN_InitStructure.CAN_BS1=CAN_BS1_3tq;      //时间段1为3个时间单位

    CAN_InitStructure.CAN_BS2=CAN_BS2_2tq;      //时间段2为2个时间单位

    CAN_InitStructure.CAN_Prescaler=60;         //时间单位长度为60 

    CAN_Init(CAN1,&CAN_InitStructure);          //波特率为:72M/2/60(1+3+2)=0.1 即波特率为100KBPs


    // CAN filter init 过滤器,注意,只接收主机发过来的数据,屏蔽其他数据

    CAN_FilterInitStructure.CAN_FilterNumber=1;                     //指定过滤器为1

    CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;   //指定过滤器为标识符屏蔽位模式

    CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;  //过滤器位宽为32位


    //CAN_FilterInitStructure.CAN_FilterIdHigh= (((u32)0x1314<

    CAN_FilterInitStructure.CAN_FilterIdHigh=0X00;                  //要过滤的ID高位 

    CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; //要过滤的ID低位 


    CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0xFFFF;            //过滤器屏蔽标识符的高16位值

    CAN_FilterInitStructure.CAN_FilterMaskIdLow=0xFFFF;             //过滤器屏蔽标识符的低16位值

    CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_FIFO0;     //设定了指向过滤器的FIFO为0

    CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;            //使能过滤器

    CAN_FilterInit(&CAN_FilterInitStructure);                       //按上面的参数初始化过滤器


    /* CAN FIFO0 message pending interrupt enable */ 

    CAN_ITConfig(CAN1,CAN_IT_FMP0, ENABLE);                         //使能FIFO0消息挂号中断

}


/* 发送两个字节的数据*/

u8 CAN_SetMsg(u8 Data1,u8 Data2)

    u8 mbox;

    u16 i=0; 

    CanTxMsg TxMessage;  


    TxMessage.StdId=0x0000;     //标准标识符为0x00

    TxMessage.ExtId=0x1311;     //扩展标识符0x1311,可以更改该标识符以示区分不同从机

    TxMessage.IDE=CAN_ID_EXT;   //使用扩展标识符

    TxMessage.RTR=CAN_RTR_DATA; //为数据帧

    TxMessage.DLC=2;            //消息的数据长度为2个字节

    TxMessage.Data[0]=Data1;    //第一个字节数据

    TxMessage.Data[1]=Data2;    //第二个字节数据 


    //发送数据

    mbox= CAN_Transmit(CAN1, &TxMessage);  

    while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))

        i++;    //等待发送结束

    if(i>=0XFFF)

        return 0;

    return 1;

}

u8 CAN_GetMsg(u8 *msg1,u8 *msg2)

{

    if(Rx_flag == 1)//发现数据

    {

        *msg1=RxBuf[0];

        *msg2=RxBuf[1];

        Rx_flag=0;//数据已经取走,可以更新数据

        return 1;

    }else

        return 0;

}

/* USB中断和CAN接收中断服务程序,USB跟CAN公用I/O,这里只用到CAN的中断。 */

void USB_LP_CAN1_RX0_IRQHandler(void)

{


  CanRxMsg RxMessage;


  RxMessage.StdId=0x00;

  RxMessage.ExtId=0x00;

  RxMessage.IDE=0;

  RxMessage.DLC=0;

  RxMessage.FMI=0;

  RxMessage.Data[0]=0x00;

  RxMessage.Data[1]=0x00;    


  CAN_Receive(CAN1,CAN_FIFO0, &RxMessage); //接收FIFO0中的数据  


  if(Rx_flag == 0)//数据已取走或者缓冲器为空

    {

        RxBuf[0]=RxMessage.Data[0];

        RxBuf[1]=RxMessage.Data[1];

        Rx_flag=1;//数据已经备好,等待取走

    }




can.h文件


#ifndef __CAN_H

#define __CAN_H


#include "sys.h"


void CAN1_Init(void);

u8 CAN_SetMsg(u8 Data1,u8 Data2);

u8 CAN_GetMsg(u8 *msg1,u8 *msg2);


#endif /* __CAN_H */




主机相关代码


这里主机代码大部分是和从机类似的,就只贴出不同的地方了。 

can.c文件:


#include "can.h"


/* 在中断处理函数中返回 */

//__IO uint32_t ret = 0;


void CAN1_Init(void)

{

    ......//以上与从机部分相同


    //CAN filter init 过滤器,已经设置为任意,可以通过ExtId标识符区分从机代号

    CAN_FilterInitStructure.CAN_FilterNumber=1;                     //指定过滤器为1

    CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;   //指定过滤器为标识符屏蔽位模式

    CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;  //过滤器位宽为32位

    CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;                //过滤器标识符的高16位值

    CAN_FilterInitStructure.CAN_FilterIdLow=CAN_ID_EXT|CAN_RTR_DATA;//过滤器标识符的低16位值

    CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;            //过滤器屏蔽标识符的高16位值

    CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;             //过滤器屏蔽标识符的低16位值

    CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_FIFO0;     //设定了指向过滤器的FIFO为0

    CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;            //使能过滤器

    CAN_FilterInit(&CAN_FilterInitStructure);                       //按上面的参数初始化过滤器


    /* CAN FIFO0 message pending interrupt enable */ 

    CAN_ITConfig(CAN1,CAN_IT_FMP0, ENABLE);                         //使能FIFO0消息挂号中断

}


//接收数据缓冲器

u8 CAN_RX_BUF[CAN_RX_LEN]={0};     //接收缓冲,最大USART_REC_LEN个字节.

//接收标志位

u8 Rx_flag=0;

/* USB中断和CAN接收中断服务程序,USB跟CAN公用I/O,这里只用到CAN的中断。 */

void USB_LP_CAN1_RX0_IRQHandler(void)

{

    u8 i=0;

    CanRxMsg RxMessage;


    RxMessage.StdId=0x00;

    RxMessage.ExtId=0x00;

    RxMessage.IDE=0;

    RxMessage.DLC=0;

    RxMessage.FMI=0;


    CAN_Receive(CAN1,CAN_FIFO0, &RxMessage); //接收FIFO0中的数据  


    if(Rx_flag == 0)//数据已取走或者缓冲器为空

    {

        if((RxMessage.DLC) == 2)//是否收到2位字节数据

        {

             CAN_RX_BUF[0]=RxMessage.Data[0];

             CAN_RX_BUF[1]=RxMessage.Data[1];     

        }

    }



/* 发送两个字节的数据*/

u8 CAN_SendMsg(u8* data1, u8* data2)

    u8 mbox;

    u16 i=0; 

    CanTxMsg TxMessage;  


    TxMessage.StdId=0x0000;     //标准标识符为0x00

    TxMessage.ExtId=0x1314;     //扩展标识符0x0000

    TxMessage.IDE=CAN_ID_EXT;   //使用扩展标识符

    TxMessage.RTR=CAN_RTR_DATA; //为数据帧

    TxMessage.DLC=2;            //消息的数据长度为2个字节

    TxMessage.Data[0]=Data1;    //第一个字节数据

    TxMessage.Data[1]=Data2;    //第二个字节数据 


    //发送数据

    mbox= CAN_Transmit(CAN1, &TxMessage);  

    while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))

        i++;    //等待发送结束

    if(i>=0XFFF)

        return 0;//发送失败

    return 1;//发送成功 

}

u8 CAN_GetMsg(u8 *msg1,u8 *msg2)

{

    if(Rx_flag == 1)//发现数据

    {

        *msg1=CAN_RX_BUF[0];

        *msg2=CAN_RX_BUF[1];

        Rx_flag=0;//数据已经取走,可以更新数据

        return 1;

    }else

        return 0;

}

void Clear_canBuffer(void)

{

    Rx_flag=0;//清楚接收标志位

    memset(CAN_RX_BUF, 0, sizeof(u8)*CAN_RX_LEN);//清空缓冲区

}

u8 Check_canRX(void)

{

    return (Rx_flag == 6);

}


can.h文件:


#ifndef __CAN_H

#define __CAN_H


#include "sys.h"

#include "string.h"


#define CAN_RX_LEN          30          //定义最大接收字节数 


extern u8  CAN_RX_BUF[CAN_RX_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 


void CAN1_Init(void);

u8 CAN_SendMsg(u8* data1, u8* data2);

u8 CAN_GetMsg(u8 *msg1,u8 *msg2);


#endif /* __CAN_H */


推荐阅读

史海拾趣

Califia Lighting公司的发展小趣事

随着产品质量的不断提升和市场份额的逐步扩大,Califia Lighting开始积极寻求与国际市场的合作。他们与多个国家和地区的合作伙伴建立了稳定的合作关系,将产品出口到全球各地。同时,公司还参加了多个国际电子照明展会,与全球同行交流学习,进一步提升了公司的国际影响力。

Frequency Electronics Inc公司的发展小趣事

为了进一步扩大市场份额,高频电子积极实施全球化战略。公司在全球范围内建立了销售网络和服务体系,与众多国际知名企业建立了长期合作关系。同时,高频电子还针对不同地区的市场需求,定制化开发符合当地标准的产品和服务。这些努力使得高频电子的产品和服务能够覆盖全球多个国家和地区,为公司带来了稳定的收入来源和持续增长的动力。

芯佰微(Corebai)公司的发展小趣事

芯佰微非常重视技术研发和专利积累。公司拥有一支高素质的研发团队,不断投入资源进行新技术和新产品的研发。同时,芯佰微也积极申请各类专利,保护自己的技术成果。经过多年的积累,芯佰微已经成功申请并获得了数十项专利,这些专利不仅提升了公司的技术实力,也为公司的未来发展提供了有力保障。

Cermetek Microelectronics公司的发展小趣事

在追求经济效益的同时,Cermetek Microelectronics公司也积极履行社会责任。公司注重环保和可持续发展,采用环保材料和生产工艺,减少对环境的影响。此外,公司还积极参与社会公益事业,为社会做出贡献。这种负责任的态度不仅赢得了社会各界的认可,也为公司的长远发展注入了正能量。

这五个故事展示了Cermetek Microelectronics公司在电子行业中的发展历程和取得的成就。通过技术积累、品质管理、持续创新、国际化战略和社会责任等方面的努力,公司逐渐在微电子领域树立起了自己的品牌形象和市场地位。

DB Unlimited公司的发展小趣事

DB Unlimited始终将客户放在心中最重要的位置。公司坚持以客户为中心的服务理念,为客户提供全方位、高品质的服务。无论是售前咨询、售后服务还是技术支持等方面,DB Unlimited都始终秉持着专业、高效、热情的态度,赢得了客户的广泛赞誉和信赖。这种客户至上的服务理念也成为了公司不断发展壮大的重要保障之一。

请注意,以上故事框架仅供参考,实际的故事需要根据DB Unlimited公司的真实发展历程和具体事实进行编写。

Elma Electronic Inc公司的发展小趣事

Elma在美国和美洲拥有广泛的客户群,覆盖电信、工业控制、医疗电子、国防和航空航天等多个行业。其产品线包括外壳、背板、系统平台、机柜、高品质开关和编码器等多种零件,能够满足不同行业客户的多样化需求。这种跨行业的布局使得Elma能够在多个领域保持竞争力。

问答坊 | AI 解惑

手机发射功率技术的专业文章

笔者从事手机测试校准系统集成有段时间,感觉到手机发射功率在不同的系统、不同的协议下有很多的不同。笔者对此深感有意思,故把PHS、GSM、cdma2000 1x、wcdma下对手机发射功率的规定罗列于此,希望能给同行起到抛砖引玉的作用,斧正我的错误。 一 ...…

查看全部问答>

数字温度传感器DS18B20的原理与应用

1引言 DS18B20是DALLAS公司生产的一线式数字温度传感器,具有3引脚TO-92小体积封装形式;温度测量范围为-55℃~+125℃,可编程为9位~12位A/D转换精度,测温分辨率可达0.0625℃,被测温度用符号扩展的16位数字量方式串行输出;其工作电源既可在 ...…

查看全部问答>

用STM32实现USB的IAP成功后,如何软复位

已经可以自动升级了,但是成功后我手工复位,真实太失败了请问如何使用指令达到复位的目的PS:不想用函数指针的方式,因为会无端用一级堆栈…

查看全部问答>

为什么这段汇编代码使用ADS1.2编译不过?

初学ARM,使用ADS1.2写了一段汇编程序,想在自己的2440裸板上跑一跑,可是汇编代码却编译不过,完整代码如下:.text.global _start_start:ldr r0, =0x53000000mov r1, #0x0str r1,[r0]ldr sp,=1024*4 b1 mainhalt_loop:b halt_loop ...…

查看全部问答>

stm32+ucosii下怎么编写串口中断程序?

stm32+ucosii下怎么编写串口中断程序? 有系统和无系统有什么不同?   谁能给个二者比较的例子?…

查看全部问答>

LPC1700 IAP编程注意事项

1.在编写IAP程序时尽量简洁,把不需要的功能尽量去掉。 2.不要用复杂的方式写IAP,越简单越好。 3.lpc芯片满足编程字节数的要求,256、512、1024,4096等,其他字数为非法,一定要注意,特别是最后一包。 [ 本帖最后由 zhaojun_xf 于 2012-3-24 1 ...…

查看全部问答>

【C2000 LaunchPad】让LED闪起来

先给个PDF文件: [ 本帖最后由 dontium 于 2012-11-27 23:36 编辑 ]…

查看全部问答>

抓取LINUX桌面的视频 FFmpeg

抓取LINUX桌面的视频 $ ffmpeg -f x11grab -s wxga -r 25 -i :0.0 -sameq /tmp/filename.mpg 我们在一些视频网站上看到别人的3D桌面怎么怎么酷的视频,通常就是这么来的,ffmpeg可以直接解码X11的图形,并转换到相应输出格式。 ffmpeg ...…

查看全部问答>

EMI强化放大器介绍

本帖最后由 dontium 于 2015-1-23 11:33 编辑 LP8755 是德州仪器(TI)推出的针对个人电子产品的业界最小型 15A、多相位 DC/DC 转换器。现在就让你工程师告诉你更多有关LP8755的信息吧!v.youku./v_show/id_XNjk0NDM5MTcy.html …

查看全部问答>

DSP F28377

我在使用F28377D的ADC模块时,发现无法输出,配置寄存器是根据用户手册来的,求教成功输出的前辈指点…

查看全部问答>