历史上的今天
返回首页

历史上的今天

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

正在发生

2019年09月30日 | 小用stm32f4-CAN控制器(使用库函数)

2019-09-30 来源:eefocus

目标实验平台:stm32f4-discovery,板载STM32F407VGT6。


写的时候忘记掉可以用“报文”这个词,于是就很愚昧的都使用了一帧信息这样的表达。。意思是一样的!


这里首先说一下CAN总线。


CAN总线时一种工业总线,展开来说就是控制器局域网,Controller Area Network


常常用在汽车和工业控制的通讯中。也就是说,在汽车上,各种控制器常常是通过CAN总线进行通讯的。


具体到他是谁发明的有什么历史就不详述了,百度百科一大堆我自己看了也记不清~


只是这两天在调两个STM32通过CAN总线进行最简单的收发通信,这里就简要介绍一下其通信的建立过程


也就是简单的收发功能,至于其中更复杂的错误检测等功能我也只看过介绍没有具体实验过,也就不说了。


如果能有机会用到,也会来再记录的。




CAN总线和I2c等总线有所不同,各个节点的通信并不是通过确定发送目标机地址来确定收发方向。


他是采取一种广播的方式,也就是说一个节点向总线发送信号,所有节点都能收到。


但是如果这个信号对自己无用怎么办呢?事实上,在CAN总线上传输的信号并不仅仅只有所需要的数据、控制信号等,


更重要的是一帧信号还有一串标识符ID,每个节点可以设置自己想要接收什么样标识符的信息


而将不需要的信息通过节点中的过滤器处理掉,就不用担心无用数据的干扰了。这样比需要地址总线通讯方式的好处就是如果同样信息不需要一个一个地址重复发送,只要一次发送就行了,提高效率。

在一个CAN的节点上,除了CPU以外,还需要一个CAN控制器和一个CAN收发器,CAN控制器可以根据当前总线上的数据传输情况以及现在控制器中发送邮箱及接受缓冲器等的情况来实时调整发送和接收。而CAN总线上的电平并不是TTL电平故需要一个CAN收发器进行电平转换的功能。


使用STM32进行CAN通讯的好处之一就是他自带有CAN控制器,而在外面只需要一个CAN收发器即可。至于其他没有CAN控制器的MCU,就需要配备如SJA1000等额外的CAN控制器了。


我这里使用的CAN收发器选用TJA1050,外部接线非常简单,如下图

其中TXD、RXD引脚连接STM32的CANTXCANRX。电源滤波电容可选取0.1uF。Vref可不接,S(RS)端可接GND或不接。至于CANHCANL之间的120欧姆电阻,在节点为CAN总线终端时接上,否则不接。电路非常简单,可以自己制作,我自己淘宝花了10块钱买了两个TJA1050芯片,其中有6元是运费。如果直接买TJA1050的模块的话,基本上网上都是10元一个,还要运费,加起来都快30了,真是不值呀~


进行双机通信时,两个CAN收发器的CANH、CANL连接即可。


如果没有条件进行双机通信,可以在STM32进行CAN控制器初始化时设置成回环模式,也就是STM32的CAN-TX在芯片内部与CAN-RX进行直接连接,而外部的CAN-RX引脚不影响内部。同时可以在外部TX引脚上通过示波器测得输出信号波形。此种方法可以在一定程度上用于测试程序正确与否(因为他隔绝了外部电路的影响,比如因为电路不正确等,但也同时无法使用其总线错误检测等功能)。建议先通过CAN的回环测试,再进行双机通信。


下面再介绍一下stm32的CAN控制器。分为CAN1(主)和CAN2(从)。每个CAN控制器有3个发送邮箱用于存放需要发送的信息。发送顺序可以由软件设置为根据信息优先级还是根据信息的请求时间。它还有2个用于接收的缓冲器队列FIFO0和FIFO1,每个FIFO可以存放3帧信息,如果存满,新来的信息可能会丢失或者被覆盖(依据软件设置)。


他可以被CAN控制器的过滤器(共28组,也就是可以设置28个标识符ID)关联,通过该过滤器的信息就会被存入相应的FIFO中。


再说一下stm32的CAN过滤器。


他共有28组过滤器,每组过滤器有2个32位的存储器。每组过滤器可以设置为:


1、两个32位完全匹配的过滤器 (用于扩展帧)2、4个16位完全匹配的过滤器 (用于标准帧)3、一个32位有位屏蔽功能的过滤器


这里再提一下,CAN总线的数据帧有两种,一种是11位标识符的,叫标准帧,另一种除了11位以外还有18位共计29位标识符的叫扩展帧。


所谓完全匹配,即总线上一帧信息的标识符必须与28个过滤器中某一个标识符每一位都相同,才会被过滤器接收到FIFO中。所谓位屏蔽,就是只需要与设定的标识符中某几位相同即可被接收进FIFO中。


絮絮叨叨说了这么多,想必对STM32的CAN控制器也有所了解了。下面根据所写的代码进行操作步骤讲解吧:D


我用的是CAN1,使用FIFO0存放接收到的数据


首先是主函数中,首先先通过CAN_Config和NVIC_Config对CAN控制器及其中断进行初始化配置。其他的CAN_打头的函数来源于STM32的函数库,具体功能见注释。

void main()  

{   

  uint8_t TransmitMailbox = 0;

 

 

  USART1_Config();//初始化串口,与电脑进行通信,方便调试  下面的printf函数被重载到发送信息到串口

  NVIC_Config();

  CAN_Config();

  RxReset();//将接收缓冲区清空

  CAN_FIFORelease(CAN1,CAN_FIFO0 );//清空接收FIFO

  PackTxMessage();//打包要发送的数据到全局结构体变量TxMessage中

  TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);//发送数据,如果发送成功返回存入的邮箱号

  while((CAN_TransmitStatus(CAN1, TransmitMailbox)  !=  CANTXOK))//判断是否成功发送

   {

     printf("nrnrStill transmittingnr");

   }

  printf("nrTransmit OK!nr");

  while(1);

}


下面是CAN_Config函数

首先需要配置使用的GPIO的复用功能,然后配置CAN控制器的收发模式以及过滤器,并打开接收中断。

配置过程可以参照STM32F4的函数库文件stm32f4xx_can.c中前面注释中的介绍“How to use this 。。。”就可以知道要对CAN控制器进行初始化需要用到哪些函数。这也是一个很重要的学习使用STM32外设的方法,就是阅读每一个库文件的源代码及其介绍。


一个经验就是,现在网上STM32的代码和相关资料大部分是针对STM32F10x芯片的,这里使用的F407的寄存器以及库函数与他们有一些不同,如果生搬硬套,首先就可能无法通过编译,提示某些变量或者函数没有定义。即使通过了编译,也很有可能因为某些在F10x中没有的函数没有使用而导致外设无法正常运行。



void CAN_Config()

{

  CAN_GPIO_Init();

  CAN_ModeAndFilterInit();

}

void CAN_GPIO_Init()

{

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//打开CAN外设时钟

  //RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1 | RCC_APB1Periph_CAN2, ENABLE);//

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

  GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_CAN1);//配置功能复用

  GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_CAN1);

  

  GPIO_InitTypeDef CANIO;

  CANIO.GPIO_Pin = GPIO_Pin_8;

  CANIO.GPIO_Mode = GPIO_Mode_AF;

  CANIO.GPIO_PuPd = GPIO_PuPd_UP;

  CANIO.GPIO_Speed = GPIO_Speed_50MHz;

  GPIO_Init(GPIOB,&CANIO);

  //Config CANTX

  CANIO.GPIO_Pin = GPIO_Pin_9;

  CANIO.GPIO_Mode = GPIO_Mode_AF;

  CANIO.GPIO_OType = GPIO_OType_PP;

  GPIO_Init(GPIOB,&CANIO);

}

void CAN_ModeAndFilterInit()

{

    CAN_InitTypeDef        CAN_InitStructure;

    CAN_FilterInitTypeDef  CAN_FilterInitStructure;

    CAN_DeInit(CAN1);//首先使用默认配置,减少配置数量

   

  /* CAN cell init */

   CAN_InitStructure.CAN_TTCM = DISABLE;//关闭时间触发通信使能(我也不知道这个干嘛用的。。)

   CAN_InitStructure.CAN_ABOM = ENABLE;//自动离线管理,也就是如果检测到总线上出错,本节点自动离线

  CAN_InitStructure.CAN_AWUM = ENABLE;//设置当需要时自动唤醒,如果DISABLE,则需要软件唤醒

   CAN_InitStructure.CAN_NART = DISABLE;//如果报文发送不成功,就自动重发

   CAN_InitStructure.CAN_RFLM = DISABLE;//在接收的FIFO中如果溢出,自动覆盖原有报文

  CAN_InitStructure.CAN_TXFP = DISABLE;//发送邮箱优先级取决于报文标识符

   CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;//正常工作模式,如果是回环模式,这里要更改为CAN_Mode_Loopback

/* CAN Baudrate = 700kbps (CAN clocked at 42 MHz) */

/*以下5行设置CAN传送速率为700kbps,CAN的最高传输速率为1Mbps*/

   CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;

CAN_InitStructure.CAN_BS1 = CAN_BS1_6tq; CAN_InitStructure.CAN_BS2 = CAN_BS2_8tq; CAN_InitStructure.CAN_Prescaler = 4; CAN_Init(CAN1, &CAN_InitStructure); /* CAN filter init *///设置CAN过滤器 CAN_FilterInitStructure.CAN_FilterNumber = 1;//使用1号过滤器,(共28组,编号0~27) CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;//标识符屏蔽位模式 CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;//32位标识符

/*以下4句分析见后文,用于设置过滤器的标识符和屏蔽位*/

   CAN_FilterInitStructure.CAN_FilterIdHigh = (((u32)0x1314<<3)&0xffff0000)>>16;//0x0000;

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

   CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xffff;

   CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0xffff;  

   

CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;//过滤器与FIFO0相关联

   CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;//开启过滤器!

   CAN_FilterInit(&CAN_FilterInitStructure);

    

   

   CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);//允许CAN的FMP0中断

 

 

}


由前文可知,CAN过滤器有2个32位存储器。她的结构如下(本程序使用一个32位有位屏蔽功能的过滤器):


第一个32位ID用于存放完整的标识符ID,第二个用于存放掩码。也就是说,在第二个32位存储器某一位若为1,则收到数据的相应位必须与之相同才会被接收,其他若为0的位不care  其中扩展ID存放在3~17位,标准ID存放在剩下的高位中。低位还有IDERTR位标明了这一帧报文的属性。IDE位标明是标准帧还是扩展帧,RTR位标明这一帧报文是数据帧还是远程遥控帧(用于请求数据)。


再看这两句:



CAN_FilterInitStructure.CAN_FilterIdHigh = (((u32)0x1314<<3)&0xffff0000)>>16;//0x0000;

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

我们的标准ID为0x00,扩展ID位0x1314,第一句FilterIdHigh即设置32位存储器的高16位,0x1314<<3后,&0xffff0000即可保留第17、18、19位,并将高位设置为0,即标准ID=0x00;最后右移16位,因为FilterIdHigh是一个16位的uint16_t类型变量。如果不保存,高位就丢失了。

懂了第一句,第二句就容易理解了,将除了最高3位以外的保留下来,再配置上IDE和RTR位,设置为扩展ID,数据帧。



 CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xffff;

   CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0xffff;  

两句设置了每一位都需要进行匹配才会被接收。如果设置成0x0000,那么任意报文都会被接收并产生接收中断。


 CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);

使能了CAN1的FMP0中断,也就是消息接收中断。除此之外还有发送中断等,详见stm32f4的库帮助手册





再回到主函数,


还未分析的函数展开如下,关于中断控制器NVIC的配置就不多叙述了。大家都懂的XD



CanTxMsg TxMessage;

CanRxMsg RxMessage;//前面两个结构体类型用于定义发送和接收的缓冲,因为CAN_Transmit()函数的参数是此种///变量

void RxReset()

{

   RxMessage.StdId = 0x00;

   RxMessage.IDE = CAN_ID_STD;

   RxMessage.DLC = 0;

    int i;

    for(i=0;i<7;i++)

    {

      RxMessage.Data[i] = 0x00;

    }

   RxMessage.ExtId = 0x00;

}

 

void PackTxMessage()//打包报文函数

{

  TxMessage.StdId = 0x00;//标准ID0x00

  TxMessage.RTR = CAN_RTR_DATA;//发送的是数据

  TxMessage.IDE = CAN_ID_EXT;//扩展帧

  TxMessage.ExtId = 0x1314;//扩展ID

  TxMessage.DLC = 2;//数据数量(最大为8)

  TxMessage.Data[0] = 0x52;//数据

  TxMessage.Data[1] = 0x6E;

}

void NVIC_Config()

{

  NVIC_InitTypeDef NVIC_InitStructure;

  NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn;

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

 

}



设置的中断函数如下:


void CAN1_RX0_IRQHandler()

{

 

  printf("nrTHIS IS INTERRUPT!nr");

  CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);

 

  printf("RxMessage:StdId=0x%x,DLC=0x%x, ExtId = 0x%x",RxMessage.StdId,RxMessage.DLC,RxMessage.ExtId);

    printf("RxMessage:data[0]=0x%x,data[1]=0x%x,",RxMessage.Data[0],RxMessage.Data[1]);

}



CAN_Receive()和CAN_Transmit()都是提供的发送接收库函数,

一旦接收缓冲区FIFO有数据,就产生一个中断,打印接收到的信息。


这段代码可以放在接收的从机里。从机中只要配置完CAN_Config和NVIC_Config即可进入中断等待。




这里的中断函数名


CAN1_RX0_IRQHandler可在startup_stm32f4xx.s汇编程序中找到入口


其中还有一个CAN1_RX1_IRQHandler


两个的不同就是,当FIFO0产生中断时,进入前者,当FIFO1产生中断时进入后者。

推荐阅读

史海拾趣

ERGOBAHCO公司的发展小趣事

ERGOBAHCO公司成立于20世纪90年代初,当时正值电子行业快速发展的黄金时期。公司创始人李明(化名)看准了市场对于高质量电子配件的需求,决定从电子连接器这一细分领域入手。然而,初创时期公司面临着资金短缺、技术落后等诸多挑战。李明凭借对市场敏锐的洞察力,成功争取到了几笔关键的投资,并带领团队攻克了一系列技术难关。通过不懈努力,ERGOBAHCO公司逐渐在电子连接器领域站稳了脚跟。

Elite Enterprises (H K) Co Ltd公司的发展小趣事

为了进一步提升公司的竞争力和市场份额,Elite Enterprises积极寻求与行业内外的合作伙伴建立战略合作关系。公司与多家知名企业签订了长期合作协议,共同开发新产品、拓展新市场。此外,公司还与一些高校和研究机构建立了产学研合作关系,共同推动LED技术的创新和应用。

艾吉芯(Agertech)公司的发展小趣事

在追求经济效益的同时,艾吉芯公司也积极履行社会责任。公司注重环境保护和可持续发展,通过采用环保材料和节能技术,降低生产过程中的能耗和排放。此外,艾吉芯还积极参与社会公益事业,为社会的和谐发展贡献自己的力量。

这些故事虽然基于推测和构建,但尽可能地反映了电子行业中企业发展的普遍规律和趋势。艾吉芯公司作为电子行业的一员,其发展历程也必然离不开这些方面的努力和探索。当然,具体的发展故事还需要根据艾吉芯公司的实际情况进行深入了解和研究。

C.K TOOLS公司的发展小趣事

在20世纪90年代初,C.K TOOLS凭借其在手工工具制造领域的技术积累,开始关注电子行业的发展趋势。随着电子产品的精密化程度不断提高,对生产工具的要求也日益严苛。C.K TOOLS针对电子组装线上的精细操作需求,研发出了一系列高精度螺丝刀和夹具,这些工具迅速在电子制造业中获得了广泛应用。通过与几家大型电子制造企业的紧密合作,C.K TOOLS逐渐在电子行业站稳了脚跟。

Chicago Miniature公司的发展小趣事

面对不断变化的市场环境和客户需求,CML始终保持创新精神。公司不断加大研发投入,推出了一系列具有创新性和竞争力的新产品。同时,CML还积极探索新的市场领域和商业模式,为公司的未来发展奠定了坚实基础。在未来,CML将继续致力于技术创新和品牌建设,努力成为全球微型照明领域的领军企业。

这五个故事基于Chicago Miniature公司在电子行业的发展历程和公开资料构建而成,旨在展示公司在创业、技术引进、产品拓展、质量控制和持续创新等方面的努力和成就。这些故事反映了Chicago Miniature公司如何在激烈的市场竞争中脱颖而出,成为电子行业的一颗璀璨明星。

Grande Electronics Ltd公司的发展小趣事
如果调制电路中的元件损坏或参数漂移,需要调整元件参数或更换新的元件。

问答坊 | AI 解惑

出现: hr -2147221164 {没有注册类别 } HRESULT

        if (dlgPush.DoModal())         {                 //得到pCERDA接口                 HRESULT hr = CoCr ...…

查看全部问答>

求助 关于MRC p15,0,R0,c0,c0,0的问题

MRC p15,0,R1,c0,c0,0这个指令是来读取ARM CPU的ID号到ARM寄存器R1里面的吗?如果是的话,我现在在EVC环境下嵌入了有下面汇编内容的.s文件:         AREA        |.text|, CODE &n ...…

查看全部问答>

高分求字库文件

我现在开发一款产品,液晶显示需要16X16中文点阵字库,考虑地区的不同,需要中文简体字库,香港的特有字库。网上找了好久也没找到合适的。各位大哥谁有发我个或提供个路径下载。…

查看全部问答>

菜鸟求助:一份正常的BSP我的电脑编译出来的系统无法正常运行?

OMAP3530 + WinCE6 R3 由于项目开始时是基于一个较早版本的BSP,现在打算更新到TI的最新BSP. 更新才刚刚开始一点,就被卡住了.先把屏的参数拷过来,屏可以正常显示,发现触摸屏没用,然后就拷过来触摸屏的一些参数,发现还是没用. 然后就开始分析了: ...…

查看全部问答>

紧急!!!!!!!!!!

有谁了解深圳\"研祥智能科技\"公司吗?那边的待遇怎么样,我是08年的应届毕业生,现在已经应聘上了那家公司,还没签协议.我很想知道,本科生在那边的待遇和发展怎么样呢? 望各位知情的大虾帮帮忙啊 ~不盛感激~…

查看全部问答>

兄弟们用过ObReferenceObjectByName吗?

在ntddk.h里怎么找不到这个函数啊,但我看好多程序都调用这个函数了…

查看全部问答>

中断标志位放在中断程序中判断有什么用?

interrupt[PORT_vector]void PORT1(void) { if(P1IFG&BIT0) {Delay(); if(P1IFG&BIT0) {执行体; P1IFG&=~BIT0; } } } 这样能去抖动吗 ,我感觉在中断程序中判断中断 ...…

查看全部问答>

数据经常要读取,不太会变,该用什么呢

EEPROM吗,读取时间长不长,还是说用其他的…

查看全部问答>

如何进行电流检测?

本帖最后由 dontium 于 2015-1-23 12:40 编辑 请问各位大牛,如果要对4~20ma的电流进行检测怎么设计比较合适?我选的一款ad芯片内阻是兆欧级别的,所以一开始设计让要检测的电流通过一个小电阻接地后,在小电阻上取电压进行检测。后来找资料的时候 ...…

查看全部问答>