STM32高级:CAN通讯案例1:环回静默模式测试 (寄存器代码)(详解)

目录

需求描述

思路:

初始化函数

GPIO引脚模块

1        RCC

2        AFIO

3        GPIO

CAN模块

1        MCR和MSR

2        MCR

发送报文

1        TSR

2        数据帧的书写(邮箱寄存器)

1        TIxR(TIR)

3        TDTxR(TDTR)

4        TDLxR/TDHxR(TDLR/TDHR)

3        TIR

6        TSR

读取报文

接收过滤器配置

can.h

can.c

main.c


需求描述

我们使用环回静默模式测试CAN能否正常工作。把接收到的报文数据发送到串口输出,看是否可以正常工作。

思路:

首先书写函数,初始化,发送报文,接收报文以及过滤器

初始化函数

初始化函数大概分为:GPIO引脚模块和CAN模块

GPIO引脚模块

1        RCC

开时钟

2        AFIO

查看原理图发现stm32can控制器对接收器也就是can的外置芯片,连接的引脚是PB8和PB9,默认是在PA11和PA12上,所以要对这两个引脚进行重定向。

3        GPIO

配置工作模式,

PB8 rx 浮空输入 mode 00 cnf 01PB9 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)
	{
	}
}

作者:山君技研轩

物联沃分享整理
物联沃-IOTWORD物联网 » STM32高级:CAN通讯案例1:环回静默模式测试 (寄存器代码)(详解)

发表回复