当前位置: 首页 > news >正文

CAN 应用编程基础-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

CAN 应用编程基础

在这里插入图片描述

CAN 基础知识

什么是 CAN?

  • 车载网络构想图

  • 概述CAN协议

    • CAN是Controller Area Network的缩写,中文称为控制器局域网络

    • 它是一种ISO国际标准化的串行通信协议

  • CAN总线的起源

    • 最初由德国电气商博世公司开发

    • 开发动机是解决现代汽车中电子控制系统之间的通讯问题,减少信号线的数量

    • 通过设计一个单一的网络总线,所有外围器件可以连接到该总线上

  • 汽车工业中的应用

    • 现代汽车对安全性、舒适性、方便性、低公害、低成本的要求推动了各种电子控制系统的发展

    • 电子控制系统包括发动机管理、变速箱控制、汽车仪表、空调、车门控制、灯光控制、气囊控制、转向控制、胎压监测、制动系统、雷达、自适应巡航、电子防盗系统等

    • 随着电子控制系统的增加,系统之间通信所需的数据类型和可靠性要求不同,多条总线结构和线束数量随之增加

  • CAN协议的发展

    • 为适应减少线束数量和实现大量数据高速通信的需求,博世公司在1986年开发了面向汽车的CAN通信协议

    • 此后,CAN通过ISO11898和ISO11519进行了标准化

    • 在欧洲,CAN已成为汽车网络的标准协议,并成为应用最广泛的现场总线之一

  • CAN的技术特点

    • CAN是一种多主方式的串行通讯总线

    • 设计规范要求高的位速率、高抗电磁干扰性,并且能够检测出任何产生的错误

    • 经过几十年的发展,CAN的高性能、高可靠性和高实时性已被广泛认可

  • CAN的应用领域

    • 被广泛应用于工业自动化、船舶、医疗设备、工业设备等领域

    • 以汽车电子为例,车上的空调、车门、发动机、大量传感器等部件和模块通过CAN总线连接,形成一个网络

CAN 的特点

  • 多主控制

    • 所有单元在总线空闲时都可以开始发送消息

    • 最先访问总线的单元获得发送权(采用CSMA/CA方式)

    • 多个单元同时发送时,发送高优先级ID消息的单元获得发送权

  • 消息发送

    • 所有消息以固定格式发送

    • 总线空闲时,所有连接单元可以开始发送新消息

    • 根据消息ID决定优先级,ID表示消息优先级而非目的地址

    • 通过逐位仲裁比较ID,仲裁获胜的单元继续发送,失败的单元停止发送并转为接收

  • 系统灵活性

    • 连接单元没有“地址”信息,增加单元时不需要改变其他单元的软硬件及应用层
  • 通信速度

    • 根据网络规模设定合适的通信速度

    • 同一网络中所有单元必须使用统一的通信速度

    • 不同网络间可以有不同的通信速度

  • 远程数据请求

    • 通过发送“遥控帧”请求其他单元发送数据
  • 错误检测与恢复

    • 所有单元可以检测错误

    • 检测到错误的单元会通知其他所有单元

    • 发送单元检测到错误会强制结束发送,并反复重发直到成功

  • 故障封闭

    • CAN能区分暂时数据错误和持续数据错误

    • 发生持续数据错误时,可将故障单元从总线上隔离

  • 连接能力

    • CAN总线可连接多个单元,理论上连接单元数无限制

    • 实际连接数受总线时间延迟和电气负载限制,通信速度与连接单元数成反比

CAN 的电气属性

  • CAN 电气属性

  • CAN总线连接

    • 使用两根线:CAN_H和CAN_L

    • 通过判断这两根线上的电位差来确定总线电平

  • 电平类型

    • CAN总线电平分为显性电平和隐性电平两种

    • 显性电平

      • 表示逻辑“0”

      • CAN_H电平为3.5V

      • CAN_L电平为1.5V

      • 电位差为2V

    • 隐性电平

      • 表示逻辑“1”

      • CAN_H和CAN_L电压都为2.5V左右

      • 电位差为0V

  • 数据传输方式

    • 通过显性和隐性电平的变化发送数据
  • 总线空闲状态

    • 当总线上没有节点传输数据时,总线处于隐性状态,即总线空闲时电平为隐性

CAN 网络拓扑

  • CAN 网络拓扑图

  • CAN总线简介

    • CAN是一种分布式控制总线,类似于以太网

    • CAN网络由多个CAN节点组成

  • 节点结构

    • 每个CAN节点非常简单,包括

      • 一个MCU(微控制器)

      • 一个CAN控制器

      • 一个CAN收发器

    • 通过CAN_H和CAN_L两根线连接在一起,形成CAN局域网络

  • 物理介质

    • CAN可以使用多种物理介质,如双绞线、光纤等

    • 最常用的是双绞线

  • 信号传输

    • 信号使用差分电压传送,线对称为CAN_H和CAN_L

    • 开发板上的CAN接口使用这两条信号线,接口也只有这两条信号线

  • 网络拓扑

    • 所有CAN节点通过CAN_H和CAN_L连接在一起

      • CAN_H接CAN_H

      • CAN_L接CAN_L

    • CAN总线两端各接一个120Ω的端接电阻,用于匹配总线阻抗,吸收信号反射及回拨,提高抗干扰能力和可靠性

  • 传输速度

    • CAN总线传输速度可达1Mbps

    • 最新的CAN-FD最高速度可达5Mbps,甚至更高

    • 传输速度与总线距离有关,总线距离越短,传输速度越快

CAN 总线通信模型

  • OSI 七层模型和 CAN 协议

  • CAN 总线协议与 OSI 模型的关系

    • CAN 总线传输协议参考了 OSI 七层模型

    • 实际上,CAN 协议只定义了“传输层”、“数据链路层”以及“物理层”这三层

  • 应用层协议

    • 应用层协议可由 CAN 用户根据特定工业领域需求自行定义

    • 常见的工业控制和制造业应用

      • DeviceNet:适用于 PLC 和智能传感器
    • 汽车工业应用

      • 各制造商有各自的应用层协议标准
  • 不同的应用层协议

    • 用于自动化技术的现场总线标准:DeviceNet

    • 工业控制:CanOpen

    • 乘用车的诊断协议:OBD、UDS(统一诊断服务,ISO14229)

    • 商用车的 CAN 总线协议:SAE J1939

  • 数据链路层

    • 分为 MAC 子层和 LLC 子层

    • MAC 子层是 CAN 协议的核心部分

    • 主要功能

      • 将物理层收到的信号组织成有意义的消息

      • 提供错误控制和传输控制的流程

      • 具体功能包括消息的帧化、仲裁、应答、错误的检测或报告

    • 通常在 CAN 控制器的硬件中执行

  • 物理层

    • 定义信号的实际发送方式、位时序、位的编码方式及同步的步骤

    • 具体未定义的内容(需用户根据系统需求确定)

      • 信号电平

      • 通信速度

      • 采样点

      • 驱动器和总线的电气特性

      • 连接器的形态

CAN 帧的种类

  • CAN通信协议报文帧类型

    • 定义了5种类型的报文帧

    • 通信通过这5种帧进行

    • 帧的种类及用途

  • 数据帧的构成

  • 数据帧和遥控帧格式

    • 数据帧

      • 使用最多的帧类型

      • 数据帧和遥控帧有标准格式和扩展格式

        • 标准格式:11位标识符(ID)

        • 扩展格式:29位标识符(ID)

      • 数据帧由 7 个段构成

        • 帧起始
          :表示数据帧开始的段

        • 仲裁段:表示该帧优先级的段

        • 控制段
          :表示数据的字节数及保留位的段

        • 数据段:
          数据的内容,可发送0~8个字节的数据

        • CRC段
          :检查帧的传输错误的段

        • ACK段
          :表示确认正常接收的段

        • 帧结束
          :表示数据帧结束的段

  • 帧结构符号

    • D:显性电平(0)

    • R:隐性电平(1)

    • D/R:显性或隐性(0或1)

  • 更加详细的内容可以参考瑞萨电子编写的《CAN入门教程》

SocketCan 应用编程

概要

  • CAN设备管理

    • 在Linux系统中,CAN设备被作为网络设备进行管理
  • SocketCAN应用编程接口

    • Linux提供了SocketCAN应用编程接口

    • 使得CAN总线通信近似于以太网通信

    • 提高了应用程序开发接口的通用性和灵活性

  • 头文件

    • SocketCAN中的大部分数据结构和函数在头文件linux/can.h中定义

    • 应用程序中必须包含<linux/can.h>头文件

创建 socket 套接字

  • CAN总线套接字的创建

    • CAN总线套接字使用标准的网络套接字操作来创建

    • 网络套接字在头文件<sys/socket.h>中定义

  • 创建CAN套接字

    • int sockfd = -1;

/* 创建套接字 */
sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(0 > sockfd) {
perror(“socket error”);
exit(EXIT_FAILURE);
}

	- 在创建套接字时,如果返回值小于0,表示创建失败- 使用perror函数输出错误信息,并调用exit(EXIT_FAILURE)退出程序- socket函数参数- 第一个参数:通信域- 在SocketCAN中,通常设置为PF_CAN,指定为CAN通信协议- 第二个参数:套接字类型- 通常设置为SOCK_RAW,表示原始套接字类型- 第三个参数:协议类型- 通常设置为CAN_RAW,指定CAN原始协议

将套接字与 CAN 设备进行绑定

  • CAN套接字与can0绑定示例

struct ifreq ifr = {0};
struct sockaddr_can can_addr = {0};
int ret;

strcpy(ifr.ifr_name, “can0”); //指定名字
ioctl(sockfd, SIOCGIFINDEX, &ifr);

can_addr.can_family = AF_CAN; //填充数据
can_addr.can_ifindex = ifr.ifr_ifindex;

/* 将套接字与 can0 进行绑定 */
ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));
if (0 > ret) {
perror(“bind error”);
close(sockfd);
exit(EXIT_FAILURE);
}

  • struct ifreq

    • 用于指定网络接口名称和获取接口索引

    • 定义在<net/if.h>头文件中

  • struct sockaddr_can

    • 用于指定CAN协议族和接口索引

    • 定义在<linux/can.h>头文件中

设置过滤规则

  • 默认接收所有ID的报文

  • 设置过滤规则的需求

    • 如果应用程序只需要接收某些特定ID的报文,或者只发送报文而不接收所有报文,可以通过setsockopt函数设置过滤规则

    • 过滤规则示例

      • 例如,某应用程序只接收ID为0x60A和0x60B的报文帧,可以设置过滤规则,将其他不符合规则的帧过滤掉

        • struct can_filter rfilter[2]; //定义一个 can_filter 结构体对象

// 填充过滤规则,只接收 ID 为(can_id & can_mask)的报文
rfilter[0].can_id = 0x60A;
rfilter[0].can_mask = 0x7FF;
rfilter[1].can_id = 0x60B;
rfilter[1].can_mask = 0x7FF;

// 调用 setsockopt 设置过滤规则
setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

		- can_filter结构体成员- struct can_filter结构体中有两个成员- can_id- can_mask- 仅发送数据,不接收报文- 如果应用程序不接收所有报文(仅发送数据),可以在内核中省略接收队列,以减少CPU资源的消耗- setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);- 这里setsockopt函数的第4个参数设置为NULL,第5个参数设置为0

数据发送/接收

  • CAN总线与标准套接字通信的差异

    • 每次通信都采用struct can_frame结构体将数据封装成帧

    • struct can_frame结构体定义

      • struct can_frame {
        canid_t can_id; /* CAN 标识符 /
        __u8 can_dlc; /
        数据长度(最长为 8 个字节) /
        __u8 __pad; /
        padding /
        __u8 __res0; /
        reserved / padding /
        __u8 __res1; /
        reserved / padding /
        __u8 data[8]; /
        数据 */
        };

      • can_id: 帧的标识符

        • 标准帧: 使用can_id的低11位

        • 扩展帧: 使用0~28位

        • 第29、30、31位是帧的标志位,用来定义帧的类型

        • /* special address description flags for the CAN_ID /
          #define CAN_EFF_FLAG 0x80000000U /
          扩展帧的标识 /
          #define CAN_RTR_FLAG 0x40000000U /
          远程帧的标识 /
          #define CAN_ERR_FLAG 0x20000000U /
          错误帧的标识,用于错误检查 */

/* mask /
#define CAN_SFF_MASK 0x000007FFU /
<can_id & CAN_SFF_MASK>获取标准帧 ID /
#define CAN_EFF_MASK 0x1FFFFFFFU /
<can_id & CAN_EFF_MASK>获取标准帧 ID /
#define CAN_ERR_MASK 0x1FFFFFFFU /
omit EFF, RTR, ERR flags */

  • (1)、数据发送

    • 发送数据

      • 使用write()函数发送数据

      • 示例:发送包含三个字节数据0xA0、0xB0、0xC0,帧ID为123

        • struct can_frame frame; //定义一个 can_frame 变量
          int ret;

frame.can_id = 123;//如果为扩展帧,那么 frame.can_id = CAN_EFF_FLAG | 123;
frame.can_dlc = 3; //数据长度为 3
frame.data[0] = 0xA0; //数据内容为 0xA0
frame.data[1] = 0xB0; //数据内容为 0xB0
frame.data[2] = 0xC0; //数据内容为 0xC0

ret = write(sockfd, &frame, sizeof(frame)); //发送数据
if(sizeof(frame) != ret) //如果 ret 不等于帧长度,就说明发送失败
perror(“write error”);

- 发送远程帧- 示例:发送远程帧(帧ID为123)- struct can_frame frame;

frame.can_id = CAN_RTR_FLAG | 123;

write(sockfd, &frame, sizeof(frame));

  • (2)、数据接收

    • 使用read()函数接收数据

    • struct can_frame frame;

int ret = read(sockfd, &frame, sizeof(frame));

  • (3)、错误处理

    • 判断can_id中的CAN_ERR_FLAG位来确定接收到的帧是否为错误帧

    • 错误帧的具体原因可以通过can_id的其他符号位判断

    • 错误帧的符号位在头文件<linux/can/error.h>中定义

      • /* error class (mask) in can_id /
        #define CAN_ERR_TX_TIMEOUT 0x00000001U /
        TX timeout (by netdevice driver) /
        #define CAN_ERR_LOSTARB 0x00000002U /
        lost arbitration / data[0] /
        #define CAN_ERR_CRTL 0x00000004U /
        controller problems / data[1] /
        #define CAN_ERR_PROT 0x00000008U /
        protocol violations / data[2…3] /
        #define CAN_ERR_TRX 0x00000010U /
        transceiver status / data[4] /
        #define CAN_ERR_ACK 0x00000020U /
        received no ACK on transmission /
        #define CAN_ERR_BUSOFF 0x00000040U /
        bus off /
        #define CAN_ERR_BUSERROR 0x00000080U /
        bus error (may flood!) /
        #define CAN_ERR_RESTARTED 0x00000100U /
        controller restarted */

回环功能设置

  • 在默认情况下,CAN的本地回环功能是开启的

  • 使用setsockopt函数设置本地回环功能

    • int loopback = 0; //0 表示关闭,1 表示开启(默认)

setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

  • 本地回环功能开启的情况下

    • 所有的发送帧都会被回环到与CAN总线接口对应的套接字上

CAN 应用编程实战

测试

  • 配置 CAN 设备,使用 cansend 命令发送数据,使用 candump 命令接收数据,利用 CAN 分析仪进行测试

CAN 数据发送实例

  • 每隔 1 秒中通过 can0 发送一帧数据,一次发送 6 个字节数据,帧 ID 为 0x123
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>int main(void)
{struct ifreq ifr = {0};	// 定义网络接口请求结构体,并初始化为0struct sockaddr_can can_addr = {0};	// 定义CAN套接字地址结构体,并初始化为0struct can_frame frame = {0};	// 定义CAN帧结构体,并初始化为0int sockfd = -1;	// 定义套接字描述符,并初始化为-1int ret;	 // 定义返回值变量/* 打开套接字 */sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);	// 创建原始CAN套接字if(0 > sockfd) {perror("socket error");exit(EXIT_FAILURE);}/* 指定can0设备 */strcpy(ifr.ifr_name, "can0");	// 设置网络接口名称为can0ioctl(sockfd, SIOCGIFINDEX, &ifr);	// 获取can0的接口索引can_addr.can_family = AF_CAN;	// 设置地址族为AF_CANcan_addr.can_ifindex = ifr.ifr_ifindex;	 // 设置接口索引/* 将can0与套接字进行绑定 */ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));if (0 > ret) {perror("bind error");close(sockfd);exit(EXIT_FAILURE);}/* 设置过滤规则:不接受任何报文、仅发送数据 */setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);/* 发送数据 */frame.data[0] = 0xA0;frame.data[1] = 0xB0;frame.data[2] = 0xC0;frame.data[3] = 0xD0;frame.data[4] = 0xE0;frame.data[5] = 0xF0;frame.can_dlc = 6;	//一次发送6个字节数据frame.can_id = 0x123;//帧ID为0x123,标准帧for ( ; ; ) {ret = write(sockfd, &frame, sizeof(frame)); //发送数据if(sizeof(frame) != ret) { //如果ret不等于帧长度,就说明发送失败perror("write error");goto out;}sleep(1);		//一秒钟发送一次}out:/* 关闭套接字 */close(sockfd);exit(EXIT_SUCCESS);
}
  • 程序的实现

    • 创建CAN套接字:使用socket函数创建一个原始CAN套接字,并检查是否成功

    • 指定CAN设备:通过strcpy函数设置网络接口名称为can0,然后使用ioctl函数获取can0的接口索引,并设置CAN套接字地址结构体

    • 绑定套接字:使用bind函数将套接字绑定到指定的CAN接口can0,并检查绑定是否成功

    • 设置过滤规则:通过setsockopt函数设置套接字选项,使得套接字不接收任何报文,仅用于发送数据

    • 准备发送数据:设置要发送的CAN帧数据,包括数据字节、数据长度码(DLC)和帧ID

    • 循环发送数据:在一个无限循环中,使用write函数发送CAN帧数据,并检查发送是否成功。如果发送失败,程序会跳转到out标签处

    • 间隔发送数据:在每次发送数据后,程序会调用sleep函数,使得数据每隔一秒钟发送一次

    • 关闭套接字并退出:在程序结束时,使用close函数关闭套接字,并调用exit函数以成功状态退出程序

CAN 数据接收实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>int main(void)
{struct ifreq ifr = {0};	// 定义网络接口请求结构体,并初始化为0struct sockaddr_can can_addr = {0};	// 定义CAN套接字地址结构体,并初始化为0struct can_frame frame = {0}; // 定义CAN帧结构体,并初始化为0int sockfd = -1;	// 定义套接字描述符,并初始化为-1int i;	// 循环变量int ret;	// 返回值变量/* 打开套接字 */sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);if(0 > sockfd) {perror("socket error");exit(EXIT_FAILURE);}/* 指定can0设备 */strcpy(ifr.ifr_name, "can0");	// 设置网络接口名称为can0ioctl(sockfd, SIOCGIFINDEX, &ifr);	// 获取can0接口索引can_addr.can_family = AF_CAN;	// 设置地址族为AF_CANcan_addr.can_ifindex = ifr.ifr_ifindex;	// 设置接口索引/* 将can0与套接字进行绑定 */ret = bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr));if (0 > ret) {perror("bind error");close(sockfd);exit(EXIT_FAILURE);}/* 设置过滤规则 *///setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);/* 接收数据 */for ( ; ; ) {if (0 > read(sockfd, &frame, sizeof(struct can_frame))) {	// 读取CAN帧数据perror("read error");break;}/* 校验是否接收到错误帧 */if (frame.can_id & CAN_ERR_FLAG) {printf("Error frame!\n");break;}/* 校验帧格式 */if (frame.can_id & CAN_EFF_FLAG)	//扩展帧printf("扩展帧 <0x%08x> ", frame.can_id & CAN_EFF_MASK);else		//标准帧printf("标准帧 <0x%03x> ", frame.can_id & CAN_SFF_MASK);/* 校验帧类型:数据帧还是远程帧 */if (frame.can_id & CAN_RTR_FLAG) {printf("remote request\n");continue;}/* 打印数据长度 */printf("[%d] ", frame.can_dlc);/* 打印数据 */for (i = 0; i < frame.can_dlc; i++)printf("%02x ", frame.data[i]);printf("\n");}/* 关闭套接字 */close(sockfd);exit(EXIT_SUCCESS);
}
  • 创建CAN套接字:使用socket函数创建一个原始CAN套接字,并检查是否成功

  • 指定CAN设备:通过strcpy函数设置网络接口名称为can0,然后使用ioctl函数获取can0的接口索引,并设置CAN套接字地址结构体

  • 绑定套接字:使用bind函数将套接字绑定到指定的CAN接口can0,并检查绑定是否成功

  • 接收数据

    • 使用无限循环持续接收CAN帧

    • 使用 read 函数读取CAN帧数据,如果读取失败,输出错误信息并退出循环

    • 校验接收到的帧是否为错误帧,如果是,输出错误信息并退出循环

    • 校验帧格式(扩展帧或标准帧)并打印帧ID

    • 校验帧类型(数据帧或远程请求帧),如果是远程请求帧,输出信息并继续下一次循环

    • 打印帧数据长度

    • 打印帧数据的每个字节

  • 关闭套接字并退出:在程序结束时,使用close函数关闭套接字,并调用exit函数以成功状态退出程序

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 华为OD-D卷找座位
  • 计算机毕业设计选题推荐-生活垃圾治理系统-Java/Python项目实战
  • 苹果应用程序清理卸载工具:App Cleaner Uninstaller Pro for Mac
  • Python设计模式 - 抽象工厂模式
  • Java学习Day20
  • RabbitMQ、Kafka对比(超详细),Kafka、RabbitMQ、RocketMQ的区别
  • 接口自动化测试框架中动态参数接口,加密接口,签名接口你们是怎么处理的?
  • TCP如何建立长连接
  • Jar工具完全指南:从入门到精通
  • C语言学习——函数
  • MySQL常用的日期和时间函数
  • oracle 比较两个值取小使用LEAST函数
  • 2024年湖北省建筑施工特种作业人员证书延期申请/年审
  • 精彩回顾 | ROS暑期“无人机自主追踪小车”训练营
  • 深度学习四大框架之争(Tensorflow、Pytorch、Keras和Paddle)
  • python3.6+scrapy+mysql 爬虫实战
  • [nginx文档翻译系列] 控制nginx
  • 【EOS】Cleos基础
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • HTTP那些事
  • JavaScript对象详解
  • Java深入 - 深入理解Java集合
  • js如何打印object对象
  • js中forEach回调同异步问题
  • mongodb--安装和初步使用教程
  • Redis在Web项目中的应用与实践
  • Spark VS Hadoop:两大大数据分析系统深度解读
  • vuex 笔记整理
  • 从tcpdump抓包看TCP/IP协议
  • 服务器从安装到部署全过程(二)
  • - 概述 - 《设计模式(极简c++版)》
  • 基于axios的vue插件,让http请求更简单
  • 我与Jetbrains的这些年
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • - 转 Ext2.0 form使用实例
  • 自制字幕遮挡器
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • #APPINVENTOR学习记录
  • #微信小程序:微信小程序常见的配置传旨
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • (1)Nginx简介和安装教程
  • (3) cmake编译多个cpp文件
  • (C++)八皇后问题
  • (C++17) std算法之执行策略 execution
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (zhuan) 一些RL的文献(及笔记)
  • (笔试题)分解质因式
  • (创新)基于VMD-CNN-BiLSTM的电力负荷预测—代码+数据
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648