CAN通信:异步通信;半双工;差分信号
闭环总线网络:高速短距离;40m;1MPBS
开环总线网络:匹配电阻区别;低速远距离;1KM;125KBPS
在理想情况下,节点不受限制;差分信号两根线的电压差来代表0,1
显性优先(0);共用总线时,同一时间只有一个节点发送其余节点接收
SS段:同步段,节点与总线时序同步1Tq
RTS段:传播时间段(1-8Tq)
PBS1段:1-8Tq
PBS2段:用于检测边沿阶位误差2-8Tq
重新同步PBS1+;PBS2-;的长度为重新同步补偿的宽度:SJW (软件可限制位数)
相位超前:PBS1+
相位滞后:PBS2-
谁先出现1谁接收
SOF:帧起始0
ID:
RTR:0数据帧;1远程请求帧
IDE:标准数据0;扩展数据1
r0:保留位0
DLC:数据长度4位(0~8)
数据段:原始数据;最高字节在前0-8个字节
CRC段:校验位15位;发送接收CRC码不同向发送方返回错误信息重新发送
CRC界定符:1;与ACK间隔
ACK槽:1
ACK段:0
ACK界定符:隔开
帧结束:7个1
- STM32 CAN1
- 发送邮箱:3个,有4个寄存器
- 接受邮箱:缓存6个,锁定模式下;FIFO溢出丢弃新数据保留原数据;非锁定模式下,新数据覆盖原数据
- 验收筛选器:两个寄存器,用于检查使用的ID
ID和掩码
掩码为1时,筛选的掩码必须要跟ID码一致
- CAN2:要使能CAN1
4种工作模式:
波特率计算:
软件流程:
- 使能CAN时钟,将对应的引脚复用映射为CAN功能APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); //打开CAN1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PA端口时钟打开
- 设置CAN工作模式;波特率等
typedef struct
{
uint16_t CAN_Prescaler; /*!< 指定时间量子长度,取值范围为1到1024 */
uint8_t CAN_Mode; /*!< 指定CAN工作模式,此参数可以是@ref CAN_operating_mode中的值 */
uint8_t CAN_SJW; /*!< 指定CAN硬件在重新同步时允许延长或缩短位的最大时间量子数,
此参数可以是@ref CAN_synchronisation_jump_width中的值 */
uint8_t CAN_BS1; /*!< 指定位段1的时间量子数,此参数可以是@ref CAN_time_quantum_in_bit_segment_1中的值 */
uint8_t CAN_BS2; /*!< 指定位段2的时间量子数,此参数可以是@ref CAN_time_quantum_in_bit_segment_2中的值 */
FunctionalState CAN_TTCM; /*!< 启用或禁用时间触发通信模式,此参数可以设置为ENABLE或DISABLE */
FunctionalState CAN_ABOM; /*!< 启用或禁用自动离线管理,此参数可以设置为ENABLE或DISABLE */
FunctionalState CAN_AWUM; /*!< 启用或禁用自动唤醒模式,此参数可以设置为ENABLE或DISABLE */
FunctionalState CAN_NART; /*!< 启用或禁用无自动重传模式,此参数可以设置为ENABLE或DISABLE */
FunctionalState CAN_RFLM; /*!< 启用或禁用接收FIFO锁定模式,此参数可以设置为ENABLE或DISABLE */
FunctionalState CAN_TXFP; /*!< 启用或禁用发送FIFO优先级,此参数可以设置为ENABLE或DISABLE */
} CAN_InitTypeDef;
- 设置CAN筛选器
typedef struct
{
uint16_t CAN_FilterIdHigh; /*!< 指定过滤器识别号(32位配置时为高16位,16位配置时为第一个ID)
此参数取值范围为0x0000至0xFFFF */
uint16_t CAN_FilterIdLow; /*!< 指定过滤器识别号(32位配置时为低16位,16位配置时为第二个ID)
此参数取值范围为0x0000至0xFFFF */
uint16_t CAN_FilterMaskIdHigh; /*!< 根据工作模式指定过滤器掩码号或识别号(32位配置时为高16位,
16位配置时为第一个掩码/ID)
此参数取值范围为0x0000至0xFFFF */
uint16_t CAN_FilterMaskIdLow; /*!< 根据工作模式指定过滤器掩码号或识别号(32位配置时为低16位,
16位配置时为第二个掩码/ID)
此参数取值范围为0x0000至0xFFFF */
uint16_t CAN_FilterFIFOAssignment; /*!< 指定将分配给该过滤器的FIFO(0或1)
此参数可以是@ref CAN_filter_FIFO中的值 */
uint8_t CAN_FilterNumber; /*!< 指定要初始化的过滤器编号,范围从0到13 */
uint8_t CAN_FilterMode; /*!< 指定要初始化的过滤器模式
此参数可以是@ref CAN_filter_mode中的值 */
uint8_t CAN_FilterScale; /*!< 指定过滤器尺度
此参数可以是@ref CAN_filter_scale中的值 */
FunctionalState CAN_FilterActivation; /*!< 启用或禁用过滤器
此参数可以设置为ENABLE或DISABLE */
} CAN_FilterInitTypeDef;
- 选择CAN中断类型,开启中断
/**
* @brief 配置CAN中断使能
* @param CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
* @param CAN_IT: 指定要配置的CAN中断源
* 可使用以下参数的组合:
* - CAN_IT_TME: 发送邮箱空中断
* - CAN_IT_FMP0: FIFO0消息挂起中断
* - CAN_IT_FMP1: FIFO1消息挂起中断
* - CAN_IT_FF0: FIFO0溢出中断
* - CAN_IT_FF1: FIFO1溢出中断
* - CAN_IT_FOV0: FIFO0满中断
* - CAN_IT_FOV1: FIFO1满中断
* - CAN_IT_EWG: 错误警告中断
* - CAN_IT_EPV: 错误被动中断
* - CAN_IT_BOF: 总线关闭中断
* - CAN_IT_LEC: 错误码变化中断
* - CAN_IT_WKU: 唤醒中断
* - CAN_IT_SLK: 睡眠中断
* @param NewState: 新状态(ENABLE或DISABLE)
* @retval 无
*/
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState)
- CAN发送和接收消息
发送:
/**
* @brief 向CAN总线发送消息
* @param CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
* @param TxMessage: 指向包含待发送消息参数的CanTxMsg结构体指针
* 结构体成员需预先配置:
* - StdId/ExtId: 标准/扩展标识符
* - IDE: 标识符类型(标准/扩展)
* - RTR: 帧类型(数据帧/远程帧)
* - DLC: 数据长度(0-8字节)
* - Data[]: 待发送数据(若为远程帧则忽略)
* @retval uint8_t: 发送邮箱状态
* - 0x00: 无空闲邮箱,发送失败
* - 0x01: 使用邮箱0,发送请求已提交
* - 0x02: 使用邮箱1,发送请求已提交
* - 0x03: 使用邮箱2,发送请求已提交
*/
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
typedef struct
{
uint32_t StdId; /*!< 指定标准标识符,取值范围为0到0x7FF */
uint32_t ExtId; /*!< 指定扩展标识符,取值范围为0到0x1FFFFFFF */
uint8_t IDE; /*!< 指定待发送消息的标识符类型,此参数可以是@ref CAN_identifier_type中的值
(注:IDE=0表示使用标准标识符StdId,IDE=1表示使用扩展标识符ExtId) */
uint8_t RTR; /*!< 指定待发送消息的帧类型,此参数可以是@ref CAN_remote_transmission_request中的值
(注:RTR=0表示数据帧,用于发送数据;RTR=1表示远程帧,用于请求数据) */
uint8_t DLC; /*!< 指定待发送帧的数据长度,取值范围为0到8(单位:字节) */
uint8_t Data[8]; /*!< 包含待发送的数据,数组中每个元素的取值范围为0到0xFF */
} CanTxMsg;
接收:
/**
* @brief 从指定CAN FIFO接收消息并存储到接收结构体中
* @param CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
* @param FIFONumber: 指定要读取的FIFO编号
* 此参数可以是以下值之一:
* - CAN_FIFO0: 读取FIFO 0中的消息
* - CAN_FIFO1: 读取FIFO 1中的消息
* @param RxMessage: 指向接收消息结构体的指针,用于存储接收到的CAN消息
* 函数会填充以下成员:
* - StdId/ExtId: 接收到的标准/扩展标识符
* - IDE: 标识符类型(标准/扩展)
* - RTR: 帧类型(数据帧/远程帧)
* - DLC: 数据长度(0-8字节)
* - Data[]: 接收到的数据(若为远程帧则Data[]内容未定义)
* - Timestamp: 接收时间戳(需启用时间戳功能)
* - FMI: 过滤器匹配索引(标识哪个过滤器匹配了该消息)
* @retval 无
*/
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage)
/**
* @brief 获取指定CAN FIFO中待处理的消息数量
* @param CANx: 指向CAN外设结构体的指针(CAN1或CAN2)
* @param FIFONumber: 指定FIFO编号
* 此参数可以是以下值之一:
* - CAN_FIFO0: FIFO 0
* - CAN_FIFO1: FIFO 1
* @retval uint8_t: 待处理的消息数量
* 返回值范围:0-3(FIFO深度为3级)
*/
uint8_t CAN_MessagePending(CAN_TypeDef* CANx, uint8_t FIFONumber)
- CAN状态获取(检测标志位)
代码如下:
#include "can.h"
#include "usart.h"
//CAN初始化
//tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq
//tbs2:时间段2的时间单元. 范围:CAN_BS2_1tq~CAN_BS2_8tq;
//tbs1:时间段1的时间单元. 范围:CAN_BS1_1tq ~CAN_BS1_16tq
//brp :波特率分频器.范围:1~1024; tq=(brp)*tpclk1
//波特率=Fpclk1/((tbs1+tbs2+1)*brp);
//mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
//Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
//则波特率为:36M/((8+9+1)*4)=500Kbps
//返回值:0,初始化OK;
// 其他,初始化失败;
void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
#if CAN_RX0_INT_ENABLE
NVIC_InitTypeDef NVIC_InitStructure;
#endif
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); //打开CAN1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PA端口时钟打开
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA12
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
//CAN单元设置
CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
CAN_InitStructure.CAN_NART=ENABLE; //使用报文自动传送
CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
CAN_InitStructure.CAN_Mode= mode; //模式设置
CAN_InitStructure.CAN_SJW=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1tq~CAN_SJW_4tq
CAN_InitStructure.CAN_BS1=tbs1; //Tbs1范围CAN_BS1_1tq ~CAN_BS1_16tq
CAN_InitStructure.CAN_BS2=tbs2;//Tbs2范围CAN_BS2_1tq ~ CAN_BS2_8tq
CAN_InitStructure.CAN_Prescaler=brp; //分频系数(Fdiv)为brp+1
CAN_Init(CAN1, &CAN_InitStructure); // 初始化CAN1
//配置过滤器
CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32位
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;////32位ID
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32位MASK
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//过滤器0关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器0
CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
#if CAN_RX0_INT_ENABLE
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE); //FIFO0消息挂号中断允许.
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 主优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 次优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
}
#if CAN_RX0_INT_ENABLE //使能RX0中断
//中断服务函数
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
int i=0;
CAN_Receive(CAN1, 0, &RxMessage);
for(i=0;i<8;i++)
printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);
}
#endif
//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧)
//len:数据长度(最大为8)
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
// 其他,失败;
u8 CAN_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x12; // 标准标识符为0
TxMessage.ExtId=0x12; // 设置扩展标示符(29位)
TxMessage.IDE=0; // 使用扩展标识符
TxMessage.RTR=0; // 消息类型为数据帧,一帧8位
TxMessage.DLC=len; // 发送两帧信息
for(i=0;i<len;i++)
TxMessage.Data[i]=msg[i]; // 第一帧信息
mbox= CAN_Transmit(CAN1, &TxMessage);
i=0;
while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++; //等待发送结束
if(i>=0XFFF)return 1;
return 0;
}
//can口接收数据查询
//buf:数据缓存区;
//返回值:0,无数据被收到;
// 其他,接收的数据长度;
u8 CAN_Receive_Msg(u8 *buf)
{
u32 i;
CanRxMsg RxMessage;
if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0; //没有接收到数据,直接退出
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据
for(i=0;i<RxMessage.DLC;i++)
buf[i]=RxMessage.Data[i];
return RxMessage.DLC;
}
主函数代码(KEY_UP_PRESS按下切换模式正常模式和回环模式;KEY1_PRESS按下,发送数据和接收数据)
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "key.h"
#include "can.h"
int main()
{
u8 i=0,j=0;
u8 key;
u8 mode=0;
u8 res;
u8 tbuf[8];
u8 rbuf[8];
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组
LED_Init();
USART1_Init(115200);
KEY_Init();
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);//500Kbps波特率
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP_PRESS) //模式切换
{
mode=!mode;
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);
if(mode==0)
{
printf("Normal Mode\r\n");
}
else
{
printf("LoopBack Mode\r\n");
}
}
if(key==KEY1_PRESS) //发送数据
{
for(j=0;j<8;j++)
{
tbuf[j]=j;
}
res=CAN_Send_Msg(tbuf,8);
if(res)
{
printf("Send Failed!\r\n");
}
else
{
printf("发送数据:");
for(j=0;j<8;j++)
{
printf("%X ",tbuf[j]);
}
printf("\r\n");
}
}
res=CAN_Receive_Msg(rbuf);
if(res)
{
printf("接收数据:");
for(j=0;j<8;j++)
{
printf("%X ",rbuf[j]);
}
printf("\r\n");
}
i++;
if(i%20==0)
{
LED1=!LED1;
}
delay_ms(10);
}
}