STM32-笔记44-CAN协议

一、什么是CAN协议?

        CAN协议,全称为 Controller Area Network (控制器局域网络),是一种广泛应用的 串行通信协议 ,最初由德国 BOSCH 公司开发,并已成为国际标准。
        CAN协议是一种基于 差分信号 的异步串行通信协议,采用双绞线作为传输介质,具有高性能、高 可靠性和独特的设计特点。

1.1、CAN协议与I2C\SPI协议的不同?

        与I2C、 SPI等具有时钟信号的同步通讯方式不同, CAN通讯并不是以时钟信号来进行同步的,它是一种异步通讯,只具有CAN_High和CAN_Low两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。
       
CAN_High: 低速CAN ISO11519 )通信速率 10~125Kbps ,总线长度可达 1000 米。
CAN_Low: 高速CAN ISO11898 )通信速率 125Kbps~1Mbps ,总线长度 40 米。

1.2 特点:

  • 多主控制:支持多主方式,即任何一个节点都可以在总线上发送数据,其他节点根据需要进行接收。当两个以上的节点同时开始发送数据时,会根据标识符(ID)决定优先级。
  • 系统柔软性:与总线相连的单元没有类似于地址的信息,因此在总线上增加单元时,连接在总线上的其他单元的软硬件及应用层都不需要改变。
  • 通信速度快、距离远:数据传输速率较高,标准速率为125kbps,扩展速率可达1Mbps,且通信距离远,最远可达10KM(速率低于5Kbps)。
  • 错误检测与恢复:具有错误检测、错误通知和错误恢复功能,能够确保数据传输的可靠性。
  • 故障封闭功能:能够判断出错误的类型,并将引起故障的单元从总线上隔离出去。

二、CAN协议分层结构

详解CAN总线:CAN协议分层结构及功能-腾讯云开发者社区-腾讯云

这里分为两层解析:

物理层和协议层

https://download.csdn.net/download/L_1068/90279533

三、STM32 CAN控制器介绍

3.1 CAN控制器介绍

3.2 CAN控制器模式

3.3 CAN控制器框图

发送处理过程

接收处理过程

接收过滤器

3.4 CAN控制器位时序

 

四、寄存器和库函数的介绍

4.1 CAN控制和状态寄存器

4.1.1 CAN主控制寄存器 (CAN_MCR)

4.1.2 CAN发送状态寄存器 (CAN_TSR)

4.1.3 CAN位时序寄存器 (CAN_BTR)

4.2 CAN邮箱寄存器

4.2.1 发送邮箱标识符寄存器 (CAN_TIxR) (x=0..2)

这里(x=0..2)代表有三个邮箱,x取值为0,1,2

4.2.2 发送邮箱数据长度和时间戳寄存器 (CAN_TDTxR) (x=0..2)

4.2.3 发送邮箱低字节数据寄存器 (CAN_TDLxR) (x=0..2)

4.2.4 发送邮箱高字节数据寄存器 (CAN_TDHxR) (x=0..2)

4.2.5 接收FIFO邮箱标识符寄存器 (CAN_RIxR) (x=0..1)

4.2.6 接收FIFO邮箱数据长度和时间戳寄存器 (CAN_RDTxR) (x=0..1)

4.2.7 接收FIFO邮箱低字节数据寄存器 (CAN_RDLxR) (x=0..1)

4.2.8 接收FIFO邮箱高字节数据寄存器 (CAN_RDHxR) (x=0..1)

4.3 CAN过滤器寄存器

4.3.1 CAN 过滤器主控寄存器 (CAN_FMR)

4.3.2 CAN 过滤器模式寄存器 (CAN_FM1R)

4.3.3 CAN 过滤器位宽寄存器 (CAN_FS1R)

4.3.4 CAN 过滤器FIFO关联寄存器 (CAN_FFA1R)

4.3.5 CAN 过滤器激活寄存器 (CAN_FA1R)

4.3.6 CAN 过滤器组i的寄存器x (CAN_FiRx) (互联产品中i=0..27,其它产品中i0..13x=1..2)

 4.4 库函数

HAL_CAN_Init();   //初始化CAN函数

HAL_CAN_ConfigFilter();  //配置过滤器

HAL_CAN_Start(); //启动CAN

HAL_CAN_AddTxMessage(); //发送数据函数

HAL_CAN_GetRxMessage();//接收数据函数

HAL_CAN_GetTxMailboxesFreeLevel(); //等待发送完成

HAL_CAN_GetRxFifoFillLevel(); //等待接收完成

五、CAN收发实验

复制项目文件19-串口打印功能

重命名为59-CAN实验

打开项目

加载文件

can.c

#include "can.h"
#include "stdio.h"

CAN_HandleTypeDef can_handle = {0};
//初始化can
void can_init(void)
{
    can_handle.Instance = CAN1;//外设的基地址
    can_handle.Init.Mode = CAN_MODE_NORMAL; //工作模式:正常模式:环回模式
    
    can_handle.Init.Prescaler = 4;//配置的BRP = 1TQ = 4
    can_handle.Init.TimeSeg1 = CAN_BS1_9TQ;//配置的TS1=8,但是这里赋值TS1=9
    can_handle.Init.TimeSeg2 = CAN_BS2_8TQ;//配置的TS1=7,但是这里赋值TS1=8
    can_handle.Init.SyncJumpWidth = CAN_SJW_1TQ;
    
    can_handle.Init.AutoBusOff           = DISABLE;  /* 禁止自动离线管理 */
    can_handle.Init.AutoRetransmission   = DISABLE;  /* 禁止自动重发 */
    can_handle.Init.AutoWakeUp           = DISABLE;  /* 禁止自动唤醒 */
    can_handle.Init.ReceiveFifoLocked    = DISABLE;  /* 禁止接收FIFO锁定 */
    can_handle.Init.TimeTriggeredMode    = DISABLE;  /* 禁止时间触发通信模式 */
    can_handle.Init.TransmitFifoPriority = DISABLE;  /* 禁止发送FIFO优先级 */
    
    HAL_CAN_Init(&can_handle);
    /*配置过滤器*/
    CAN_FilterTypeDef can_filterconfig = {0};
    can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK;//掩码的格式
    can_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT;//宽度 扩展帧 32位
    
    can_filterconfig.FilterIdHigh = 0;//每个过滤器组包含2个32位的寄存器CAN_FxR1和CAN_FxR2都配置为0,不使用
    can_filterconfig.FilterIdLow = 0;
    can_filterconfig.FilterMaskIdHigh = 0;//无任何过滤
    can_filterconfig.FilterMaskIdLow = 0;
    
    can_filterconfig.FilterBank = 0;//指定哪个过滤器
    can_filterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;//fifo0中
    can_filterconfig.FilterActivation = CAN_FILTER_ENABLE;//使能过滤器
    can_filterconfig.SlaveStartFilterBank = 14;//固定位14位
    HAL_CAN_ConfigFilter(&can_handle, &can_filterconfig);//过滤器
    
    HAL_CAN_Start(&can_handle);//can正常工作
}
//初始化can msp函数
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
    __HAL_RCC_CAN1_CLK_ENABLE();//使能CAN的时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIO口
    
    GPIO_InitTypeDef gpio_initstruct;
    
    gpio_initstruct.Pin = GPIO_PIN_12;    //tx      
    gpio_initstruct.Mode = GPIO_MODE_AF_PP; //复用推挽输出            
    gpio_initstruct.Pull = GPIO_PULLUP;                     
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;           
    HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    
    gpio_initstruct.Pin = GPIO_PIN_11;    //rx      
    gpio_initstruct.Mode = GPIO_MODE_AF_INPUT;             
    HAL_GPIO_Init(GPIOA, &gpio_initstruct);
}
//发送数据
void can_send_data(uint32_t id,uint8_t *buf,uint8_t len)
{
    CAN_TxHeaderTypeDef tx_header = {0};
    uint32_t tx_mail = CAN_TX_MAILBOX0;//使用的邮箱
    tx_header.ExtId = id;//扩展帧id从外界传进来
    tx_header.DLC = len;//数据长度,外界传进来
    tx_header.IDE = CAN_ID_EXT; //扩展id
    tx_header.RTR = CAN_RTR_DATA;//数据帧还是遥控帧
    HAL_CAN_AddTxMessage(&can_handle, &tx_header,buf, &tx_mail);//发送数据函数
    
    while(HAL_CAN_GetTxMailboxesFreeLevel(&can_handle) != 3);//判断数据发送完成,三个邮箱等于3的时候退出循环
    
    uint8_t i = 0;
    printf("发送数据:\r\n");
    for(i = 0; i < len; i++)
        printf("%X ", buf[i]);//把发送的数据给打印出来
    printf("\r\n");
}
//接收数据
uint8_t can_receive_data(uint8_t *buf)
{
    CAN_RxHeaderTypeDef rx_header = {0};
    
    if(HAL_CAN_GetRxFifoFillLevel(&can_handle, CAN_RX_FIFO0) == 0)//先看邮箱里面是否有数据
        return 0;
    
    HAL_CAN_GetRxMessage(&can_handle, CAN_RX_FIFO0, &rx_header, buf);//接收数据函数,从FIFO0中取数据,RX帧的头部存储在&rx_header结构体中
    
    uint8_t i = 0;//打印接收的数据
    printf("接收数据:\r\n");
    for(i = 0; i < rx_header.DLC; i++)
        printf("%X ", buf[i]);
    printf("\r\n");
    
    return rx_header.DLC;
}

can.h

#ifndef __CAN_H_
#define __CAN_H_

#include "sys.h"

void can_init(void);
void can_send_data(uint32_t id,uint8_t*buf,uint8_t len);
uint8_t can_receive_data(uint8_t *buf);

#endif

main.c

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "can.h"
#include "key.h"

uint8_t data_send[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};//发送八个数据
uint8_t data_receive[8];//接收八个数据

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    led_init();//初始化led灯
    uart1_init(115200);
    printf("hello word!\r\n");
    can_init();
    key_init();
    
uint8_t i = 0;
    while(1)
    { 
        if(key_scan() == 1)
        {
            for(i = 0; i < 8; i++)
                data_send[i]++;//将数据每一位加1
            
            can_send_data(0x12345678, data_send, 8);//指定id,指定发送的数据,发送数据的长度
        }
        
        can_receive_data(data_receive);//接收发送的数据
    }
}

回环模式结果显示(按下key1键即可发送数据)

 从回环模式到正常模式。只需要更改如下 模式要求

正常模式显示

因为我这里只有一个上官一号和TJA1050,所以只能看见发送数据。正常使用正常模式时需要两个上官一号和TJA1050,将TJA1050使用杜邦线连接在上官一号的引脚上,并且两个TJA1050使用杜邦线连接,这个就是总线,在实现CAN数据收发模拟的时候,需要打开两个串口助手,一个观察发送,一个观察接收,按下key1键即可发送,这是双向的过程。

 正常情况下的截图如下:按下key1键即可发送数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值