目录
需求描述
我们使用环回静默模式测试CAN能否正常工作。把接收到的报文数据发送到串口输出,看是否可以正常工作。
思路:
首先书写函数,初始化,发送报文,接收报文以及过滤器
初始化函数
初始化函数大概分为:GPIO引脚模块和CAN模块
GPIO引脚模块
1 RCC
开时钟
2 AFIO
查看原理图发现stm32can控制器对接收器也就是can的外置芯片,连接的引脚是PB8和PB9,默认是在PA11和PA12上,所以要对这两个引脚进行重定向。
3 GPIO
配置工作模式,
PB8 rx 浮空输入 mode 00 cnf 01,PB9 tx 复用推挽输出 mode 11 cnf 10
CAN模块
1 MCR和MSR
看需求是环回静默模式通过手册,配置测试模式中的循环静默模式需要先配置初始化模式。
要配置初始化模式可以看到进入是两步置1和等待,退出是清0和等待,需要MCR和MSR
2 MCR
看到初始化要至少涉及两个寄存器MCR
通过查看可以知道MCR单独配置要位6和位5以及位1(退出睡眠)
3 BTR
在手册中测试模式下环会静默模式通过BTR,更加印证了初始化模式所设计到的BTR。
SILM和LBKM置1。
4 BTR
BRP:确定分频器:我们要设置一个时间单元1微秒,CAN是在APB1上所以是36MHz,推出我们要设置36分频,寄存器要加1,所以这里配置35。
TS1和TS2:配置相位缓冲段的时间单元,首先CAN将每位分为4个段,但是在STM32中,传播时间段和相位缓冲段1合并,总共我们要配置10个时间单元,这里在寄存器中,将相位缓冲段1和相位缓冲段2配置位2和5(因为这里+1了)。
配置同步跳跃宽度为1(+1特性)。
发送报文
CAN的发送和接收是由邮箱来控制的,根据手册中的发送处理
1 TSR
通过等待TME0位是否等于0,进行等待发送。
2 数据帧的书写(邮箱寄存器)
五个部分:ID,RTR(什么帧),IDE(格式),DLC,DATA,包装成函数参数只有三个ID、DLC(长度)、DATA。
1 TIxR(TIR)
ID和两个设置写入TIR中,记得先清0。
3 TDTxR(TDTR)
DLC写入到TDTR中,记得先清0
4 TDLxR/TDHxR(TDLR/TDHR)
将发送的数据写在TDLR(低4字节)TDHR(高4字节)中。
3 TIR
TXRQ位,请求发送数据。
6 TSR
TXOK0位等待发送完成。
读取报文
首先需要定义一个结构体,结构体里由ID、数据以及长度
根据手册可以看到使用了RFR寄存器
1 RF0R
通过FMP0位产看报文数量
2 RIR
读取报文ID
3 RDTR
读取数据长度
4 RDLR/RDHR
读取数据
5 RF0R
通过RF0M0 释放,再读取下一个报文
接收过滤器配置
1 FMR
FINIT:进入初始化
2 FM1R
FBMx:过滤器模式:0标识符屏蔽模式,1标识符列表模式
3 FS1R
FSCx:过滤器位宽设置:
4 FFA1R
FFA0:过滤完要进入哪个接收邮箱
5 FR1
保存ID的寄存器
6 FR2
保存掩码的寄存器
7 FA1R
FACT0:表示激活0的过滤器,相当于使能位
8 FMR
FINIT:退出初始化
can.h
#ifndef __CAN_H
#define __CAN_H
#include "stm32f10x.h"
// 定义结构体,保存接收到的报文信息
typedef struct
{
uint16_t stdID;
uint8_t data[8];
uint8_t len;
} RxMsg;
// 初始化
void CAN_Init(void);
// 发送报文
void CAN_SendMsg(uint16_t stdID, uint8_t * data, uint8_t len);
// 接收报文
void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t * msgCount);
#endif
can.c
#include "can.h"
static void CAN_FilterConfig(void);
// 初始化
void CAN_Init(void)
{
// 1. 开启时钟:CAN、GPIO、AFIO
RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 2. 重映射PB8、PB9
AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1;
AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0;
// 3. GPIO工作模式定义:
// PB8 - 浮空输入 MODE 00,CNF 01
GPIOB->CRH &= ~GPIO_CRH_MODE8;
GPIOB->CRH &= ~GPIO_CRH_CNF8_1;
GPIOB->CRH |= GPIO_CRH_CNF8_0;
// PB9 - 复用推挽输出, MODE 11,CNF 10
GPIOB->CRH |= GPIO_CRH_MODE9;
GPIOB->CRH |= GPIO_CRH_CNF9_1;
GPIOB->CRH &= ~GPIO_CRH_CNF9_0;
// 4. CAN 初始化基本配置
// 4.1 进入初始化模式
CAN1->MCR |= CAN_MCR_INRQ;
while ((CAN1->MSR & CAN_MSR_INAK) == 0)
{}
// 4.2 退出睡眠模式
CAN1->MCR &= ~CAN_MCR_SLEEP;
while ((CAN1->MSR & CAN_MSR_SLAK) != 0)
{}
// 4.3 自动离线管理
CAN1->MCR |= CAN_MCR_ABOM;
// 4.4 自动唤醒管理
CAN1->MCR |= CAN_MCR_AWUM;
// 4.5 配置环回静默测试模式
CAN1->BTR |= CAN_BTR_SILM;
CAN1->BTR |= CAN_BTR_LBKM;
// 4.6 配置位时序
// 4.6.1 配置波特率分频器,36分频,Tq = 1us
CAN1->BTR &= ~CAN_BTR_BRP;
CAN1->BTR |= (35 << 0);
// 4.6.2 配置BS1和BS2的时间长度
CAN1->BTR &= ~CAN_BTR_TS1;
CAN1->BTR |= (2 << 16);
CAN1->BTR &= ~CAN_BTR_TS2;
CAN1->BTR |= (5 << 20);
// 4.6.3 再同步跳跃宽度
CAN1->BTR &= ~CAN_BTR_SJW;
CAN1->BTR |= (1 << 24);
// 4.7 退出初始化模式
CAN1->MCR &= ~CAN_MCR_INRQ;
while ((CAN1->MSR & CAN_MSR_INAK) != 0)
{}
// 5. CAN过滤器配置
CAN_FilterConfig();
}
// 定义静态函数,进行过滤器配置
static void CAN_FilterConfig(void)
{
// 1. 进入初始化模式
CAN1->FMR |= CAN_FMR_FINIT;
// 2. 配置过滤器工作模式:0 - 屏蔽位模式
CAN1->FM1R &= ~CAN_FM1R_FBM0;
// 3. 配置位宽:1 - 32位
CAN1->FS1R |= CAN_FS1R_FSC0;
// 4. 设置关联的FIFO:FIFO0
CAN1->FFA1R &= ~CAN_FFA1R_FFA0;
// 5. 设置过滤器组0的ID寄存器:FR1
CAN1->sFilterRegister[0].FR1 = 0x06e << 21;
// 6. 设置过滤器组0的掩码寄存器:FR2 = 0,不做过滤,全部接收
CAN1->sFilterRegister[0].FR2 = 0x7f1 << 21;
// 7. 激活过滤器组0
CAN1->FA1R |= CAN_FA1R_FACT0;
// 8. 退出初始化模式
CAN1->FMR &= ~CAN_FMR_FINIT;
}
// 发送报文:标准ID、数据、长度(使用发送邮箱0)
void CAN_SendMsg(uint16_t stdID, uint8_t * data, uint8_t len)
{
// 1. 等待发送邮箱0为空
while ( (CAN1->TSR & CAN_TSR_TME0) == 0)
{
}
// 2. 包装要发送的数据帧
// 2.1 设置标准ID
CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;
CAN1->sTxMailBox[0].TIR |= stdID << 21;
// 2.2 设置为标准帧
CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE;
// 2.3 设置为数据帧
CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR;
// 2.4 设置数据长度
CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;
CAN1->sTxMailBox[0].TDTR |= len << 0;
// 2.5 设置数据
// 先清零寄存器
CAN1->sTxMailBox[0].TDLR = 0;
CAN1->sTxMailBox[0].TDHR = 0;
// 循环处理每一个字节数据
for (uint8_t i = 0; i < len; i++)
{
// 判断前4个字节,放到TDLR中
if (i < 4)
{
CAN1->sTxMailBox[0].TDLR |= data[i] << (i * 8);
}
// 后4个字节,放到TDHR中
else
{
CAN1->sTxMailBox[0].TDHR |= data[i] << ((i - 4) * 8);
}
}
// 3. 请求发送数据帧
CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;
// 4. 等待发送完成
while ((CAN1->TSR & CAN_TSR_TXOK0) == 0)
{
}
}
// 接收报文:结构体数组,数组长度(从FIFO0中读取)
void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t * msgCount)
{
// 1. 获取FIFO0的报文个数,通过指针返回
* msgCount = (CAN1->RF0R & CAN_RF0R_FMP0) >> 0;
// 2. 循环读取每一个报文
for (uint8_t i = 0; i < *msgCount; i++)
{
// 定义指针,指向当前保存报文的数据对象
RxMsg * msg = &rxMsg[i];
// 2.1 读取ID
msg->stdID = (CAN1->sFIFOMailBox[0].RIR >> 21) & 0x7ff;
// 2.2 读取数据长度
msg->len = (CAN1->sFIFOMailBox[0].RDTR >> 0) & 0x0f;
// 2.3 读取数据
uint32_t low = CAN1->sFIFOMailBox[0].RDLR;
uint32_t high = CAN1->sFIFOMailBox[0].RDHR;
for (uint8_t j = 0; j < msg->len; j++)
{
// 如果是前4个字节,就从RDLR中提取
if (j < 4)
{
msg->data[j] = (low >> (8 * j)) & 0xff;
}
// 如果是后4个字节,就从RDHR中提取
else
{
msg->data[j] = (high >> (8 * (j - 4))) & 0xff;
}
}
// 2.4 释放FIFO0,出队,再读取下一个报文
CAN1->RF0R |= CAN_RF0R_RFOM0;
}
}
main.c
#include "usart.h"
#include "can.h"
#include <string.h>
int main(void)
{
// 初始化
USART_Init();
CAN_Init();
printf("尚硅谷CAN通讯实验:环回静默模式测试,寄存器版...\n");
// 1. 发送三个报文
uint16_t stdID = 0x066;
uint8_t * data = "abcdefg";
CAN_SendMsg(stdID, data, strlen((char *)data));
stdID = 0x068;
data = "123";
CAN_SendMsg(stdID, data, strlen((char *)data));
stdID = 0x067;
data = "xyz";
CAN_SendMsg(stdID, data, strlen((char *)data));
// 2. 接收数据
RxMsg rxMsg[3];
uint8_t msgCount;
CAN_ReceiveMsg(rxMsg, &msgCount);
printf("报文接收完毕!count = %d\n", msgCount);
// 3. 打印输出报文
for (uint8_t i = 0; i < msgCount; i++)
{
printf("stdID = %#X, len = %d, data = %.*s\n",
rxMsg[i].stdID, rxMsg[i].len, rxMsg[i].len, rxMsg[i].data);
}
while (1)
{
}
}
标签:初始化,BTR,报文,寄存器,STM32,环回,CAN1,data,MCR
From: https://blog.csdn.net/qq_64219867/article/details/144673719