stm32:CAN通讯

目录

介绍

物理层​编辑

差分信号

总线网络

协议层

CAN的 帧/报文 种类

数据帧

远程帧(遥控帧)

错误帧

过载帧

帧间隔

总线仲裁

位同步

数据同步

波特率

stm32的CAN外设

工作模式

测试模式

功能框图

时序

标准时序

例子  环回静默模式测试

寄存器代码

HAL版本


介绍

一种功能丰富的车用总线标准。被设计用于在不需要主机(Host)的情况下,允许网络上的单片机和仪器相互通信

它基于消息传递协议,设计之初在车辆上复用通信线缆,以降低铜线使用量,后来也被其他行业所使用

CAN拥有了良好的弹性调整能力,可以在现有网络中增加节点而不用在软、硬件上做出调整。除此之外,消息的传递不基于特殊种类的节点,增加了升级网络的便利性

物理层

  • 一个CAN控制器
    • 一般MCU提供,stm32内部提供了一个can控制器
  • 一个CAN收发器
    • 收发器一般需要专门芯片提供,例如PD1050S收发器芯片
  1. 发数据
    1. 控制器把要发送的二进制编码通过CAN_Tx线发送到收发器
    2. 收发器会把逻辑电平信号转化为差分信号,通过差分信号线CAN_High和CAN_Low线输出到CAN总线网络
  2. 收数据
    1. 收发器把总线上的CAN_High和CAN_Low信号转化成普通的逻辑电平信号
    2. 通过CAN_Rx输出到控制器中

差分信号

差分传输是一种信号传输的技术,差分传输在这两根线上都传输信号,这两个信号的振幅相同,相位相反,简单来说就是一高一低,互相相反

信号接收端比较这两个电压的差值来判断发送端发送的逻辑状态,插值为-0.5--0.05表示0,插值为1.5--3表示1

在电路板上,差分走线必须是等长、等宽、紧密靠近、且在同一层面的两根线。

总线网络

当CAN线上接入多个设备时,就构成了CAN总线网络,根据接法不同,总线网络分2种 

  • 闭环总线网络
    • 速度快,距离近
  • 开环总线网络
    • 速度慢,距离远

协议层

CAN的 帧/报文 种类

特点:

  1.  CAN总线是广播类型的总线
  2. 这意味着所有节点都可以侦听到所有传输的报文
  3. 无法将报文单独发送给指定节点
  4. 所有节点都将始终捕获所有报文
  5. 但是CAN硬件能够提供本地过滤功能,让每个节点对报文有选择性地做出响应
  6. CAN使用短报文 – 最大实用负载是94位
  7. 可以认为报文是通过内容寻址,也就是说,报文的内容隐式地确定其地址
  8. CAN总线上有5种不同的报文类型
    1. 数据帧,远程帧,错误帧,过载帧,帧间隔

数据帧

 

  • 数据帧是最常见的报文类型,用于发送单元向接收单元发送数据
  • 有标准格式与扩展格式。标准格式有11位标识符,扩展格式有29位标识符
  •  仲裁段
    • 标识符位
      • 长度为11位,ID10~ID0,按照10-0的顺序进行传输
      • 是一个动能性的地址,CAN接收器通过标识符来过滤数据帧,表示自己的id
      • 不同节点的标识符是不能相同的
    • 远程发送请求位
      • 用于区分该帧是数据帧还是远程帧
      • 显性信号为0表示数据帧,远程帧为1
    • ssr位
      • 替代RTR,隐性信号
    • ide位
      • 表示扩展帧还是标准帧
    • rtr位
      • 表示数据帧还是远程帧
  • 控制段
    • ide位
      • 区分标准格式与扩展格式
      • 显性电平时表示标准格式
      • 隐性电平时表示扩展格式
    • R1位RO位
      • 保留位
    • DLC
      • 表示报文中的数据段含有多少个字节
  • 数据段
    • 发送的数据,高位先行
  • crc段
    • 校验码
      • 用于校验传输的正不正确
      • 硬件自动生成
    • 界定符
      • 隐性位,表示校验码的结束
  • ACK段
    • 确认位
      • 隐性位
      • 接收端收到正确的CRC校验位后,把这一位置为显性位
    • 界定符
      • 隐性位,表示与后面的帧结束隔开

远程帧(遥控帧)

  • 远程帧用于接收单元向具有相同id的发送单元请求发送数据
  • 有标准格式与扩展格式。标准格式有11位标识符,扩展格式有29位标识符
  • 与数据帧相比没有数据段
    • DLC都为0
    • 其他和数据帧一样

错误帧

  • 错误帧当检测出错误时向其他单元通知错误的帧
  • 由硬件自动完成的,没有办法用软件来控制

过载帧

  • 过载帧并不常用,因为当今的CAN控制器会非常智能化地避免使用过载帧
  • 由硬件自动完成的,没有办法用软件来控制

帧间隔

  • 用于将数据帧及遥控帧与前面的帧分离开来的帧
  • 由硬件自动完成的,没有办法用软件来控制

总线仲裁

发送接收特点:

  1. CAN总线处于空闲状态的时候,最先发送消息的单元获得发送权
  2. 多个单元同时开始发送时,从仲裁段(报文id)的第一位开始进行仲裁
  3. 连续输出显性电平最多的单元可以继续发送,即首先出现隐形电平的单元失去最总线的占有权变为接收。(即报文id小的优先级高)
  4. 竞争失败,会自动检测总线空闲,在第一时间再次尝试发送

位同步

CAN提供了位同步的方式来确保通讯时序

一帧包含了很多位,CAN把每一位分为4段,同步段,传播时间段,相位缓冲段1,相位缓冲段2

4段的总时间段构成了位时间,就是传输一个位所需的总时间

位时间通常被分为若干等长的时间单元,称为时间量化器(Tq)

一个Tq的长度可以根据传输速率的需要设置

  • 同步段
    • 固定1Tq
    • 若通讯节点检测到总线上信号的跳变沿被包含在同步段的范围之内,则表示节点与总线的时序是同步的
  • 传播时间段
    • can总线会受到各种物理延迟
    • 比如发送单元的发送延迟、总线上信号的传播延迟、接收单元的输入延迟等
    • 作用是补偿延迟
    • 至少1Tq,一般为1-8
  • 相位缓冲段1
    • 主要用来补偿边沿阶段的误差。
    • 它的时间长度在重新同步阶段的时候可以被自动加长
    • 一般为1-8Tq
  • 相位缓冲段2
    • 另一个相位缓冲段,也是用来补偿边沿阶段误差的
    • 它的时间长度在重新同步阶段时可以缩短
    • 一般为2-8Tq

数据同步

根据同步方式差异,CAN的数据同步分为硬同步和再同步

所有同步由硬件自动完成

  • 硬同步
    • 当一个节点检测到起始位时,它会执行硬同步,以便将其内部的时间基准与数据帧的时间基准对齐
    • 在硬同步过程中,控制器的时间基准会立即调整为与检测到的边沿对齐
    • 在帧起始段同步
  • 再同步
    • 在检测到总线上的时序与节点使用的时序有相位差时(就是发生在同步段)
    • 通过延长相位缓冲段1或者缩短相位缓冲段2来获得同步

波特率

各个通讯节点约定好1个Tq 的时间长度以及每一位占据多少个 Tq,就可以确定CAN通讯的波特率。

假设1Tq=1us,而每个数据位由20个Tq组成,就是传输一位要20us,每秒就可以传输50000位,波特率就为50kbps

stm32的CAN外设

STM32的芯片中具有bxCAN控制器,它支持CAN协议2.0A 和2.0B Active标准。

  • CAN2.0A只能处理标准数据帧且扩展帧的内容会织别错误。
  • 而CAN2.0 B Active可以处理标准数据帧和扩展数据帧。
  • CAN2.0 B Passive只能处理标准数据帧而扩展帧的内容会被忽略
  • 该CAN控制器支持最高的通讯速率为1Mb/s
  • 可以自动地接收和发送CAN报文
  • 外设中具有3个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间
  • 具有2个3级深度的接收FIFO,可使用过滤功能只接收或不接收某些ID号的报文
  • 可配置成自动重发;不支持使用DMA进行数据收发

工作模式

CAN控制器有3种工作模式:

  • 初始化模式
  • 正常模式
  • 睡眠模式

上电复位后CAN控制器默认会进入睡眠模式,作用是降低功耗。当需要将进行初始的时候(配置寄存器),会进入初始化模式。当需要通讯的时候,就进入正常模式

测试模式

有3种测试模式:

  • 静默模式
  • 环回模式
  • 环回静默模式

当控制器进入初始化模式的时候才可以配置测试模式

  • 静默模式可以用于检测总线的数据流量
  • 环回模式可以用于自检(影响总线)
  • 环回静默也是用于自检,不会影响到总线

功能框图

  • 主动内核
    • 含各种控制/状态/配置寄存器,可以配置模式、波特率等。在STM32CubeMx中可以非常方便的配置
  • 发送邮件
    • 用来缓存待发送的报文,最多可以缓存3个报文。发送调度决定报文的发送顺序
  • 接收FIFO
    • 共有2个接收FIFO,每个FIFO都可以存放3个完整的报文。它们完全由硬件来管理。从而节省了CPU的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取FIFO输出邮箱,来读取FIFO中最先收到的报文
  • 接收滤波器
    • 做用:对接到的报文进行过滤。最后放入FIFO 0或FIFO 1
    • 当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用CPU。过滤器的存在,选择性接收有效报文,减轻系统负担
    • 有2种过滤模式
      • 标识符列表模式
        • 它把要接收报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理
      • 掩码模式(屏蔽位模式)
        • 它把可接收报文ID的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码(关键字)相同,就符合要求,报文就会被保存到接收FIFO

时序

标准时序

  • 与我们前面解释的 CAN 标准时序有一点区别
  • STM32的位时序:把传播时间段和相位缓冲段1做了合并

例子  环回静默模式测试

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

寄存器代码

main.c

int main(void)
{
    usart1_init();
    printf("尚硅谷 CAN 通讯实验: 静默回环 寄存器版\r\n");
    CAN_Init();
    printf("CAN 初始化配置完成...\r\n");

    uint16_t stdId = 0x066;
    uint8_t *tData = "abcdefg";
    CAN_SendMsg(stdId, tData, strlen((char *)tData));
    printf("发送完毕...\r\n");

    tData = "123";
    CAN_SendMsg(stdId, tData, strlen((char *)tData));
    printf("发送完毕...\r\n");

    stdId = 0x067;
    tData = "xyz";
    CAN_SendMsg(stdId, tData, strlen((char *)tData));
    printf("发送完毕...\r\n");

    /* 1. 接收数据 */
    RxDataStruct rxDataStruct[8];
    uint8_t rxMsgCount;
    CAN_ReceiveMsg(rxDataStruct, &rxMsgCount);
    printf("接收完毕 rxMsgCount = %d...\r\n", rxMsgCount);

    /* 2. 输出消息 */
    uint8_t i;
    for (i = 0; i < rxMsgCount; i++)
    {
        RxDataStruct msg = rxDataStruct[i];
        printf("stdId = %d, length = %d, msgData = %s\r\n", msg.stdId, msg.length, msg.data);
    }
    while (1)
    {

    }
}

can.h

#ifndef __CAN_H
#define __CAN_H
#include "stm32f10x.h"
#include "usart.h"
#include "string.h"

/**
 * @description: 存储接收到的数据
 * @return {*}
 */
typedef struct
{
    uint16_t stdId;
    uint8_t data[8];
    uint8_t length;
} RxDataStruct;

void CAN_Init(void);
void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length);
void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount);

#endif

can.c

#include "can.h"

/**
 * @description: CAN 通讯初始化
 */
void CAN_Init(void)
{
    /* 1. 开启时钟 CAN时钟和GPIO时钟 */
    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;

    /* 2. 重定向PB8和PB9引脚  10:CAN_RX映像到PB8,CAN_TX映像到PB9 */
    AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1;
    AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0;

    /* 3. 初始化GPIO:
        PB9(CAN_Tx):复用推挽输出 mode=11 cnf=10
        PB8(CAN_Rx): 浮空输入 mode=00 cnf=01
    */
    GPIOB->CRH |= GPIO_CRH_MODE9;  /* mode = 11 */
    GPIOB->CRH |= GPIO_CRH_CNF9_1; /* cnf = 10 */
    GPIOB->CRH &= ~GPIO_CRH_CNF9_0;

    GPIOB->CRH &= ~GPIO_CRH_MODE8;  /* mode = 00 */
    GPIOB->CRH &= ~GPIO_CRH_CNF8_1; /* cnf = 01 */
    GPIOB->CRH |= GPIO_CRH_CNF8_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 配置位时序寄存器 */
    /* 4.5.1 静默模式 用于调试 */
    CAN1->BTR |= CAN_BTR_SILM;
    /* 4.5.2 回环模式 用于调试 */
    CAN1->BTR |= CAN_BTR_LBKM;
    /* 4.5.3 波特率分频器,定义Tq的长度。
     配置35表示36分频,则产生波特率的时钟位1MHz。
     Tq = 1us
    */
    CAN1->BTR &= ~CAN_BTR_BRP; /* 相应的位均置0 (9:0) */
    CAN1->BTR |= 35 << 0;
    /* 4.5.4 时间段1(3*Tq)和时间段2(6*Tq) */
    CAN1->BTR &= ~CAN_BTR_TS1;
    CAN1->BTR &= ~CAN_BTR_TS2;
    CAN1->BTR |= (3 << 16);
    CAN1->BTR |= (6 << 20);
    /* 4.5.5 再同步跳跃宽度 2*Tq*/
    CAN1->BTR &= ~CAN_BTR_SJW;
    CAN1->BTR |= (2 << 24);

    /* 4.6 退出初始化模式 */
    CAN1->MCR &= ~CAN_MCR_INRQ;
    while ((CAN1->MSR & CAN_MSR_INAK) != 0) /* 等待退出初始化模式 */
        ;
    /* 4.7 配置过滤器: 接收所有消息 */
    /* 4.7.1 进入过滤器初始化模式 */
    CAN1->FMR |= CAN_FMR_FINIT;
    /* 4.7.2 过滤器组0工作模式: 掩码模式 0:掩码模式  1:标识符模式 */
    CAN1->FM1R &= ~CAN_FM1R_FBM0;
    /* 4.7.2 过滤器组0为单个32位配置 0:2给16位  1:单个32位*/
    CAN1->FS1R |= CAN_FS1R_FSC0;
    /* 4.7.3 给过滤器组0分配FIFO 0:FIFO0 1:FIFO1. 通过后的报文会放入这个FIFO中*/
    CAN1->FFA1R &= ~CAN_FFA1R_FFA0;
    /* 4.7.4 设置过滤器组0 标识符寄存器FR1 */
    CAN1->sFilterRegister[0].FR1 = 0x00000000; /* id每位都是0 */
    /* 4.7.5 设置过滤器组0 屏蔽位寄存器FR2 */
    CAN1->sFilterRegister[0].FR2 = 0x00000000; /* 屏蔽位是0,表示不关心ID对应的位。都是0,表示接收所有消息 */
    /* 4.7.6 激活过滤器组0 */
    CAN1->FA1R |= CAN_FA1R_FACT0;
    /* 4.7.7 退出过滤器初始化模式 */
    CAN1->FMR &= ~CAN_FMR_FINIT;
}

/**
 * @description: 发送消息
 * @param {uint16_t} stdId 标准帧id
 * @param {uint8_t} *data 要发送的数据
 * @param {uint8_t} length 发送的数据的字节数
 */
void CAN_SendMsg(uint16_t stdId,
                 uint8_t *data,
                 uint8_t length)
{
    if (length > 8)
    {
        printf("数据长度不能超过8个字节\r\n");
        return;
    }
    /* 1. 等待邮箱0为空 (也可以判断其他邮箱) 0:非空 1:空*/
    while ((CAN1->TSR & CAN_TSR_TME0) == 0)
        ;

    /* 2. 使用标准标识符 0:标准标识符 1:扩展标识符 */
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE;

    /* 3. 0:数据帧 or 1:远程帧 */
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR;

    /* 4. 设置标准标识符 */
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;
    CAN1->sTxMailBox[0].TIR |= (stdId << 21);

    /* 5. 设置数据长度 */
    CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;
    CAN1->sTxMailBox[0].TDTR |= (length << 0);

    /* 6. 设置数据 */
    uint8_t i;
    CAN1->sTxMailBox[0].TDLR = 0; /* 低位寄存器 */
    CAN1->sTxMailBox[0].TDHR = 0; /* 高位寄存器 */
    for (i = 0; i < length; i++)
    {
        if (i < 4)
        {
            CAN1->sTxMailBox[0].TDLR |= (data[i] << (8 * i));
        }
        else
        {
            CAN1->sTxMailBox[0].TDHR |= (data[i] << (8 * (i - 4)));
        }
    }

    /* 7. 请求发送数据 */
    CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;
}

/**
 * @description:
 * @param {uint16_t} *stdId 读取数据的标准id
 * @param {uint8_t} *data 读取到的数据
 * @param {uint8_t} *length 读取到的数据的长度
 */
void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount)
{
    /* 1. 获取 FIFO0 中的报文数 */
    *msgCount = (CAN1->RF0R & CAN_RF0R_FMP0) >> 0;

    uint8_t i, j;
    for (i = 0; i < *msgCount; i++)
    {
        RxDataStruct *msg = &rxDataStruct[i];
        /* 2. 读取标准标识符id */
        msg->stdId = (CAN1->sFIFOMailBox[0].RIR >> 21) & 0x7FF;

        /* 3. 读取数据长度 */
        msg->length = (CAN1->sFIFOMailBox[0].RDTR >> 0) & 0x0F;

        /* 4. 读取数据 */
        memset(msg->data, 0, sizeof((char *)msg->data));
        uint32_t low = CAN1->sFIFOMailBox[0].RDLR;
        uint32_t high = CAN1->sFIFOMailBox[0].RDHR;
        for (j = 0; j < msg->length; j++)
        {
            if (j < 4)
            {
                msg->data[j] = (low >> (8 * j)) & 0xFF;
            }
            else
            {
                msg->data[j] = (high >> (8 * (j - 4))) & 0xFF;
            }
        }
        /* 5. 释放 FIFO 0. 则报文数减1*/
        CAN1->RF0R |= CAN_RF0R_RFOM0;
    }
}

HAL版本

 

main.c

int main(void)
{
   
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_CAN_Init();
    MX_USART1_UART_Init();
    /* USER CODE BEGIN 2 */
    /* 1. 配置过滤器 */
    CAN_Filter_Config();
    /* 2. 启动CAN总线 */
    HAL_CAN_Start(&hcan);

    /* 3. 发送数据 */
    uint16_t stdId = 0x011;
    uint8_t *tData = "abcdefg";
    CAN_SendMsg(stdId, tData, strlen((char *)tData));
    printf("发送完毕...\r\n");
    tData = "123";
    CAN_SendMsg(stdId, tData, strlen((char *)tData));
    printf("发送完毕...\r\n");

    /* 4. 接收数据 */
    RxDataStruct rxDataStruct[8];
    uint8_t rxMsgCount;
    CAN_ReceiveMsg(rxDataStruct, &rxMsgCount);
    printf("接收完毕 rxMsgCount = %d...\r\n", rxMsgCount);

    /* 5. 输出消息 */
    uint8_t i;
    for (i = 0; i < rxMsgCount; i++)
    {
        RxDataStruct msg = rxDataStruct[i];
        printf("stdId = %d, length = %d, msgData = %s\r\n", msg.stdId, msg.length, msg.data);
    }

    while (1)
    {
    }
}

can.h中添加

 /* USER CODE BEGIN Prototypes */
    typedef struct
    {
        uint16_t stdId;
        uint8_t data[8];
        uint8_t length;
    } RxDataStruct;

    void CAN_Filter_Config(void);
    void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length);
    void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount);
    /* USER CODE END Prototypes */

can.c中添加

/* USER CODE BEGIN 1 */
/**
 * @description: 配置过滤器
 */
void CAN_Filter_Config()
{
    CAN_FilterTypeDef sFilterConfig;

    sFilterConfig.FilterBank = 0;                      // 过滤器编号, CAN1是0-13, CAN2是14-27
    sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  // 采用掩码模式
    sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 设置筛选器的尺度, 采用32位
    sFilterConfig.FilterIdHigh = 0X0000;               // 过滤器ID高16位,即CAN_FxR1寄存器的高16位
    sFilterConfig.FilterIdLow = 0X0000;                // 过滤器ID低16位,即CAN_FxR1寄存器的低16位
    sFilterConfig.FilterMaskIdHigh = 0X0000;           // 过滤器掩码高16位,即CAN_FxR2寄存器的高16位
    sFilterConfig.FilterMaskIdLow = 0X0000;            // 过滤器掩码低16位,即CAN_FxR2寄存器的低16位
    sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 设置经过筛选后数据存储到哪个接收FIFO
    sFilterConfig.FilterActivation = ENABLE;           // 是否使能本筛选器
    sFilterConfig.SlaveStartFilterBank = 14;           // 指定为CAN1分配多少个滤波器组

    HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
}

/**
 * @description: 发送信息
 * @param {uint16_t} stdId
 * @param {uint8_t} *data
 * @param {uint8_t} length
 */
void CAN_SendMsg(uint16_t stdId,
                 uint8_t *data,
                 uint8_t length)
{
    /* 1. 检测发送邮箱是否可用 */
    while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0)
        ;

    CAN_TxHeaderTypeDef txHeader;
    txHeader.IDE = CAN_ID_STD;   // 标准帧还是扩展帧
    txHeader.RTR = CAN_RTR_DATA; // 帧的类型: 数据帧还是远程帧
    txHeader.StdId = stdId;      // 标准帧的id
    txHeader.DLC = length;       // 发送的数据长度 单位字节
    uint32_t txMailBox;          // 会把这次使用的邮箱存入到这个变量
                                 /* 2. 发送消息 */
    HAL_CAN_AddTxMessage(&hcan, &txHeader, data, &txMailBox);
}

/**
 * @description: 接收消息
 * @param {RxDataType} *
 */
void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount)
{
    /* 1. 检测FIFO0收到的报文个数 */
    *msgCount = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0);

    /* 2. 遍历出所有消息 */
    uint8_t i;
    CAN_RxHeaderTypeDef rxHeader;
    for (i = 0; i < *msgCount; i++)
    {
        HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rxHeader, rxDataStruct[i].data);
        rxDataStruct[i].stdId = rxHeader.StdId;
        rxDataStruct[i].length = rxHeader.DLC;
    }
}
/* USER CODE END 1 */

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/843082.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应用层——HTTP

像我们电脑和手机使用的应用软件就是在应用层写的&#xff0c;当我们的数据需要传输的时候换将数据传递到传输层。 应用层专门给用户提供应用功能&#xff0c;比如HTTP,FTP… 我们程序员写的一个个解决我们实际的问题都在应用层&#xff0c;我们今天来聊一聊HTTP。 协议 协议…

游泳耳机哪个牌子最好?公认最好的四大游泳耳机测评分享

游泳耳机&#xff0c;作为水上运动爱好者的贴心伴侣&#xff0c;已被公认为提升水下体验的利器。然而&#xff0c;在抖音、贴吧等社交平台上&#xff0c;我们也不难发现一些关于游泳耳机性能不佳、使用效果差&#xff0c;甚至对耳朵造成不适的反馈。这种矛盾现象的出现&#xf…

HTML5大作业三农有机,农产品,农庄,农旅网站源码

文章目录 1.设计来源1.1 轮播图页面头部效果1.2 栏目列表页面效果1.3 页面底部导航效果 2.效果和源码2.1 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_4…

基于pytorch演练线性回归模型

引言 本文的目的是在前文基于numpy演练可视化梯度下降的代码基础上&#xff0c;使用pytorch来实现一个功能齐全的线性回归训练模型。 为什么仍然使用线性回归模型&#xff1f; 线性回归模型简单&#xff0c;它能让我们聚集在pytorch是如何工作的&#xff0c;而不是模型内部的…

使用百度语音技术实现文字转语音

使用百度语音技术实现文字转语音 SpringBootVue前后端分离项目 调用api接口需要使用AK和SK生成AccessToken,生成getAccessToken的接口有跨域限制,所以统一的由后端处理了 部分参数在控制台->语音技术->在线调试里面能找到 Controller RestController RequestMapping(&q…

C++ | Leetcode C++题解之第268题丢失的数字

题目&#xff1a; 题解&#xff1a; class Solution { public:int missingNumber(vector<int>& nums) {int n nums.size();int total n * (n 1) / 2;int arrSum 0;for (int i 0; i < n; i) {arrSum nums[i];}return total - arrSum;} };

探索 Framer Motion 高级动画技巧:提升前端设计水平

在现代的网页和应用设计中&#xff0c;动画不仅仅是视觉的点缀&#xff0c;更是用户体验的重要组成部分。它能够使界面更具吸引力&#xff0c;提升交互的流畅性&#xff0c;甚至在不经意间传达品牌的个性和态度。然而&#xff0c;要创造出令人惊叹的动效并不容易——直到有了 F…

Edge侧边栏copilot消失

Edge侧边栏copilot消失 当前环境 自己ip问题已解决&#xff0c;edge中已登录账号&#xff0c;地区已设置为美国&#xff0c;语言已设置为英文。具体可以通过空白页右上角的setting验证 解决方案 首先&#xff0c;打开“任务管理器”&#xff0c;在其中找到 Microsoft Edge…

【SASS/SCSS(三)】样式的复用与动态计算(@mixin和@function)

目录 一、mixin 1、定义复用的样式代码&#xff0c;接受传参&#xff0c;搭配include使用。 位置传参 关键词传参 ...语法糖接受传入的任意参数 2、在mixin中使用content&#xff0c;获取外部对mixin的追加内容 二、function 三、字符串——值得注意的点 很多时候&#…

[Doris]阿里云搭建Doris,测试环境1FE 1BE

首先&#xff1a;阿里云的国内服务器千万不要用容器搭建&#xff0c;或者自己Dockfile构建镜像。两种方式都不得行&#xff0c;压根拉不到github的镜像&#xff0c;开了镜像加速器也拉不到&#xff0c;不要折腾了&#xff0c;极其愚蠢。 背景&#xff1a;现在测试环境&#xff…

算法力扣刷题记录 五十六【501.二叉搜索树中的众数】

前言 二叉搜索树操作&#xff0c;继续。 记录 五十六【501.二叉搜索树中的众数】 一、题目阅读 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#xff09;…

【BUG】已解决:zipfile.BadZipFile: File is not a zip file

已解决&#xff1a;zipfile.BadZipFile: File is not a zip file 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开发…

IAR环境下STM32+IAP方案的实现

--基于STM32F103ZET6的UART通讯实现 一、什么是IAP&#xff0c;为什么要IAP IAP即为In Application Programming(在应用中编程)&#xff0c;一般情况下&#xff0c;以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了&#xff0c;如果在设备使…

Day16_集合与迭代器

Day16-集合 Day16 集合与迭代器1.1 集合的概念 集合继承图1.2 Collection接口1、添加元素2、删除元素3、查询与获取元素不过当我们实际使用都是使用的他的子类Arraylist&#xff01;&#xff01;&#xff01; 1.3 API演示1、演示添加2、演示删除3、演示查询与获取元素 2 Iterat…

ros笔记03--从零体验ros2话题通信方式

ros笔记03--从零体验ros2话题通信方式 介绍创建步骤体验官方 talker listener 案例基于python开发发布订阅案例 注意事项说明 介绍 主题是 ros2 提供的三种主要接口方式之一&#xff0c;它通常被用于连续的数据流&#xff0c;如传感器数据、机器人状态等。 ros2 是一个强类型的…

Artix7系列FPGA实现SDI视频编解码+UDP以太网传输,基于GTP高速接口,提供工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本博已有的以太网方案本博已有的FPGA图像缩放方案本方案的缩放应用本方案在Xilinx--Kintex系列FPGA上的应用本方案在Xilinx--Zynq系列FPGA上的应用 3、详细设计方案设计原理框图SDI 输入设备Gv8601a 均衡…

SAP Fiori 实战课程(二):新建页面

课程回顾 上一课中,利用Visual studio Code 新建、并运行了一个Demo工程。可以实现对项目的启动,启动后进入一个List清单。 那么本次课程的目前就是在上一节Demo的基础上,从零开始新建一个完整的页面。实现从首页清单,选择行后,鼠标点击,进入下一个页面。 准备工作 在开…

【20】读感 - 架构整洁之道(二)

概述 继上一篇文章讲了前两章的读感&#xff0c;已经归纳总结的重点&#xff0c;这章会继续跟进的看一下&#xff0c;深挖架构整洁之道。 编程范式 编程范式从早期到至今&#xff0c;提过哪些编程范式&#xff0c;结构化编程&#xff0c;面向对象编程&#xff0c;函数式编程…

想要获客如有神助攻,宝藏工具必不可少!

现如今&#xff0c;客户资源的收集和管理成为了一个让很多人都为之烦恼的问题。 然而&#xff0c;随着科技的进步&#xff0c;市场上出现了许多高效的宝藏工具&#xff0c;可以帮助你轻松解决这些问题&#xff0c;让获客如有神助攻&#xff01; 1、丰富的客户来源 无论是附近…

TypeScript体操(二):Utility Type手写实现

目录 前言常用 Utility Types 及其实现Partial<T>Required<T>Readonly<T>Pick<T, K>Omit<T, K>Record<K, T>Exclude<T, U>Extract<T, U>NonNullable<T>ReturnType<T>InstanceType<T>Parameters<T>Con…