一、什么是CAN协议?
1.1、CAN协议与I2C\SPI协议的不同?
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,其它产品中i=0..13; x=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键即可发送数据