使用STM32F103做CAN的收发通信

2023-05-16

下面也是搭建嵌入式系统所必须的一个部分。

参考网站:https://www.cnblogs.com/craigtao/p/3645148.html
https://blog.csdn.net/qq_29413829/article/details/53230716

以下部分内容来自于上述网站。

一、can通信

CAN 是Controller Area Network 的缩写(以下称为CAN),是ISO国际标准化的串行通信协议。
该通信使用的是ISO11898标准,该标准的物理层特征如图1所示:

这里写图片描述

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<<3)&0xFFFF0000)>>16;  
    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 */


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用STM32F103做CAN的收发通信 的相关文章

  • CAN报文解析—案例

    1 CAN报文定义 CAN报文是指发送单元向接受单元传送数据的帧 我们通常所说的CAN报文是指在CAN线 xff08 内部CAN 整车CAN 充电CAN xff09 上利用ECU和CAN卡接收到的十六进制报文 2 CAN协议中CAN报文种类
  • 蓝牙通信、串口通信、和can通信以及MCU之间的关系

    说明 xff1a 其端口和can需要设置波特率 xff1a 250 500 xff0c 和can的类型 xff0c can1 can2 串口直接和模拟量通信需要设置串口的信息 xff0c 如下图 xff1a 蓝牙和模拟量通信 xff0c 不
  • STM32F103 PB3,PB4,PA15,IO不可控问题

    STM32默认启动时PB4 PB3 PA15三个引脚不是普通IO xff0c 而是JTAG的复用功能 xff0c 分别为JNTRST JTDI JTDO 由上可以知要使PB3可以用 须关闭JTAG DP SW DP 可以不管 添加以下配置即
  • CAN为什么会发送失败

    CAN总线调试过程中出现报文发送失败 xff0c 很多工程师都对此只知其一不知其二 xff0c 这里就CAN报文发送失败的问题我们来做一次探讨 在了解CAN报文为什么会发送失败之前我们先看看一条正确的CAN报文到底应该是怎么样的 xff0c
  • rplidar_ros 报错:can‘t bind 和Operation Time Out的解决

    我使用的思岚A2的雷达在ros下运行 1 can t bind无法连接的错误 xff0c 一种是设备号不匹配引起的错误 xff0c 首先可以使用ll dev grep ttyUSB查看一下设备的dev号 xff0c 再检查一下rplidar
  • CAN 与 CANFD数据报文格式解读

    ISO 11898 1 2015 协议详细定义了CAN网络报文的格式 xff0c 包含了传统CAN与CANFD帧格式 CAN网络的帧类型共有4种 xff0c 分别为数据帧 远程帧 错误帧 过载帧 本章将主要介绍CAN网络数据帧报文的格式 x
  • CAN学习笔记3:STM32 CAN控制器介绍

    STM32 CAN控制器 1 概述 STM32 CAN控制器 bxCAN 支持CAN 2 0A 和 CAN 2 0B Active版本协议 CAN 2 0A 只能处理标准数据帧且扩展帧的内容会识别错误 而CAN 2 0B Active 可以
  • stm32f103c8t6+dht11测量温湿度(单总线协议)

    核心问题 如何根据电路和时序来写程序 接口协议 单总线 意味着一根数据线实现接收和发送 所以相应的GPIO口要设置为2种模式 输入 输出 电路 如果没有在电路中作5k上拉 可以把GPIO模式设置成GPIO Mode IPU实现软件上拉的效果
  • STM32F103移植FreeRTOS必须搞明白的系列知识---3(堆栈)

    STM32F103移植FreeRTOS必须搞明白的系列知识 1 Cortex CM3中断优先级 STM32F103移植FreeRTOS必须搞明白的系列知识 2 FreeRTOS任务优先级 STM32F103移植FreeRTOS必须搞明白的系
  • STM32CUBEMX F103 HAL库开发 两路定时器的Encoder编码器模式

    机器人开发过程中 对于直流电机来说 编码器至关重要 它不仅可以使我们对电极进行精确的速度闭环 位置闭环 还可以通过时间积分 根据运动学关系 获得速度 位置等信息 STM32的定时器有编码器模式 大大的方便我们的开发 使用STM32cubeM
  • MCP2515独立控制器

    1 简介 MCP2515 是一款独立控制器局域网络 Controller Area Network CAN 协议控制器 完全支持 CAN V2 0B 技术规范 该器件能发送和接收标准和扩展数据帧以及远程帧 MCP2515 自带的两个验收屏蔽
  • STM32进入STOP模式并唤醒实验总结

    项目需求 需要实现设备低功耗功能 实现过程中遇到几个问题 以此记录总结 stm32f103ret6 问题一 执行PWR EnterSTOPMode PWR Regulator LowPower PWR STOPEntry WFI 后 程序继
  • STM32串口配置实验

    STM32 串口简介 串口作为 MCU 的重要外部接口 同时也是软件开发重要的调试手段 其重要性不言而喻 现在基本上所有的 MCU 都会带有串口 STM32 自然也不例外 STM32 的串口资源相当丰富的 功能也相当强劲 ALIENTEK
  • CAN总线的EMC设计方案

    一 CAN接口EMC设计概述 Controller Area Network简称为CAN 多用于汽车以及工业控制 用于数据的传输控制 在应用的过程中通讯电缆容易耦合外部的干扰对信号传输造成一定的影响 单板内部的干扰也可能通过电缆形成对外辐射
  • stm32f103 TIM2定时器4路PWM输出实验

    这里以TIM2为例 pwm c include pwm h uint16 t TIM2 CCR1 Val uint16 t TIM2 CCR2 Val uint16 t TIM2 CCR3 Val uint16 t TIM2 CCR4 Va
  • STM32学习心得三十二:CAN通信基础知识、原理、配置及实验

    记录一下 方便以后翻阅 主要内容 1 CAN通信基础知识 2 STM32 CAN控制器简介 3 相关实验代码解读 参考资料 STM32中文参考手册 V10 第22章 控制器局域网 bxCAN 实验功能 CAN实验需要两个开发板 系统启动后
  • CAN 为什么需要收发器

    在RTL代码中集成了两个CAN node 打算直接连接将两个node的Rx和Tx对接 发现两个CAN Node无法通信 询问技术支持后才知道必须要收发器 那为什么一定需要收发器呢 除了转换单端的CAN信号用于不同的传输 收发器也会将CANT
  • Ubuntu 周立功CAN分析仪 USBCAN-II 驱动

    首先从官网https www zlg cn Index Search search key linux的下载资料界面下载 Linux驱动 USBCAN I I II II 2A I MINI安装驱动 USBCAN II新版驱动基于libus
  • STM32F407 CAN1 CAN2

    注意CAN1可以单独使用 CAN2要开启CAN1时钟才能使用 u8 CAN1 Mode Init u8 mode GPIO InitTypeDef GPIO InitStructure CAN InitTypeDef CAN InitStr
  • pico示波器使用

    文章目录 Pico示波器保存波形 Pico示波器录制数据 Pico示波器解析CAN报文 Pico示波器保存波形 Pico示波器可以通过以下步骤保存波形 在示波器上选择要保存的波形 连接示波器到计算机上 可以使用USB或者Ethernet连接

随机推荐