stm32:CAN通讯

目录

介绍

协议层

CAN的 帧/报文 种类

数据帧

远程帧(遥控帧)

错误帧

过载帧

帧间隔

总线仲裁

stm32的CAN外设

工作模式

测试模式

功能框图

时序

标准时序

例子  环回静默模式测试

寄存器代码

HAL版本


介绍

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

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

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

物理层

  • 一个CAN控制器
    • 一般MCU提供,stm32内部提供了一个can控制器
  • 一个CAN收发器
    • 收发器一般需要专门芯片提供,例如PD1050S收发器芯片

can 

协议层

CAN的 帧/报文 种类

特点:

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

数据帧

  • 数据帧是最常见的报文类型,用于发送单元向接收单元发送数据
  • 有标准格式与扩展格式。标准格式有11位标识符,扩展格式有29位标识符

远程帧(遥控帧)

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

错误帧

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

过载帧

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

帧间隔

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

总线仲裁

发送接收特点:

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

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=10PB8(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-27sFilterConfig.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; // 设置经过筛选后数据存储到哪个接收FIFOsFilterConfig.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;      // 标准帧的idtxHeader.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 */

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

24暑假算法刷题 | Day15 | LeetCode 110. 平衡二叉树,257. 二叉树的所有路径,404. 左叶子之和,222. 完全二叉树的节点个数

目录 110. 平衡二叉树题目描述题解 257. 二叉树的所有路径题目描述题解 404. 左叶子之和题目描述题解 222. 完全二叉树的节点个数题目描述题解 110. 平衡二叉树 点此跳转题目链接 题目描述 给定一个二叉树&#xff0c;判断它是否是平衡二叉树 平衡二叉树 是指该树所有节点的…

Redis-布隆过滤器(Bloom Filter)详解

文章目录 什么是布隆过滤器 布隆过滤器的优点&#xff1a;布隆过滤器的缺点&#xff1a;其他问题 布隆过滤器适合的场景布隆过滤器原理 数据结构增加元素查询元素删除元素 如何使用布隆过滤器 Google开源的Guava自带布隆过滤器Redis实现布隆过滤器 Redis中配置布隆过滤器Redis…

【echarts】tooltip 增加单位

单个柱子 const data [{value: 1,per: 2},{value: 22,per: 2},{value: 222,per: 3} ];tooltip: {trigger: axis,show: true,axisPointer: {type: line,lineStyle: {color: rgba(0, 0, 0, 0.03),type: solid,width: 60,},},formatter(params) {return ${params[0].name}: ${par…

数据结构历年考研真题对应知识点(树、森林)

目录 5.4.2树、森林与二叉树的转换 1.树转换为二叉树 【树和二叉树的转换及相关性质的推理(2009、2011)】 2.森林转换为二叉树 【森林和二叉树的转换及相关性质的推理(2014)】 3.二叉树转换为森林 【由遍历序列构造一棵二叉树并转换为对应的森林(2020、2021)】 5.4.3树和…

C# 各版本语法新功能汇总

C# 8.0 以后 官网 C# 7.3 》》in C# 7.2 》》 命名参数、具名参数 》》》 条件 ref 表达式 C# 7.1 》》 default 运算符 default 在C#7.1中得到了改进&#xff0c;不再需要default&#xff08;T&#xff09;了 //变量赋值C#7.0 var s "字符串"; s default(s…

LeetCode 算法:电话号码的字母组合 c++

原题链接&#x1f517;&#xff1a;电话号码的字母组合 难度&#xff1a;中等⭐️⭐️ 题目 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 …

SpringCloud教程 | 第九篇: 使用API Gateway

1、参考资料 SpringCloud基础篇-10-服务网关-Gateway_springcloud gateway-CSDN博客 2、先学习路由&#xff0c;参考了5.1 2.1、建了一个cloudGatewayDemo&#xff0c;这是用来配置网关的工程&#xff0c;配置如下&#xff1a; http://localhost:18080/aaa/name 该接口代码如…

git clone命令, 克隆远程仓库

这个应该是最简单的命令&#xff0c;当别人扔给你一个*****.git链接&#xff0c;你要知道怎么用&#xff0c;但是还需要注意以下几点&#xff1a; 1. 你在该网站上是否有账号 2. 你在该网站上的账号是否是该项目的成员&#xff0c;如果不是&#xff0c;那可能clone不了 3. 本机…

WSL-Ubuntu20.04部署环境配置

1.更换Ubuntu软件仓库镜像源 为了在WSL上使用TensorRT进行推理加速&#xff0c;需要安装以下环境&#xff0c;下面将按以下顺序分别介绍安装、验证以及删除环境&#xff1a; #1.C环境配置 gcc、gdb、g #2.gpu环境 cuda、cudnn #3.Cmake环境 CMake #4.OpenCV环境 OpenCV #5.Ten…

vxe-grid 实现配置式form搜索条件 form搜索条件框可折叠 配置式table

文章目录 效果图代码 效果图 代码 <template><div class"app-container"><vxe-grid refxGrid v-bind"gridOptions" v-if"tableHeight" :height"tableHeight"><template #billDate"{ data }"><e…

Zoom视频会议软件使用

Zoom是一款广受欢迎的视频会议软件&#xff0c;使用它可以轻松地进行远程会议、在线培训和团队协作等。要充分利用Zoom软件的功能&#xff0c;以下是详细具体的使用方法和步骤&#xff1a; 下载安装 下载&#xff1a;访问Zoom官方网站&#xff0c;根据使用的操作系统下载相应的…

ttkefu在线客服系统 机器人+人工客服 全渠道接入客户咨询

ttkefu在线客服系统是一种集成了机器人客服与人工客服&#xff0c;并支持全渠道接入客户咨询的综合解决方案。这种系统能够显著提升客户服务效率&#xff0c;优化客户体验&#xff0c;同时帮助企业降低运营成本 1. 智能机器人客服 自动回复&#xff1a;机器人客服能够自…

自制连点器

B站使用教程&#xff1a;https://www.bilibili.com/video/BV1SR85e4EKw/?vd_source47eba1800d831e86d4778a128740fe73 下载链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1Spv_yVPFB3zoS__VL-nhaQ?pwdyxo1 提取码&#xff1a;yxo1

20.x86游戏实战-远线程注入的实现

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

Spring AI 应用开发中设置访问 Ollama 的超时时间

使用 Spring AI 开发 AI 应用时&#xff0c;Ollama 通常在本地开发和测试时使用&#xff0c;用来在本地运行大模型。由于本地开发机器的资源限制&#xff0c;当使用 Ollama 运行较大的模型时&#xff0c;大模型给出响应的时间会比较长。Spring AI 提供的 OllamaChatModel 与 Ol…

在Mac上免费恢复误删除的Word文档

Microsoft Word for Mac是一个有用的文字处理应用程序&#xff0c;它与Microsoft Office套件捆绑在一起。该软件的稳定版本包括 Word 2019、2016、2011 等。 Word for Mac 与 Apple Pages 兼容;这允许在不同的操作系统版本中使用Word文档&#xff0c;而不会遇到任何麻烦。 与…

【数据结构】非线性表----树详解

树是一种非线性结构&#xff0c;它是由**n&#xff08;n>0&#xff09;**个有限结点组成一个具有层次关系的集合。具有层次关系则说明它的结构不再是线性表那样一对一&#xff0c;而是一对多的关系&#xff1b;随着层数的增加&#xff0c;每一层的元素个数也在不断变化&…

逆向案例二十三——请求头参数加密,某区块链交易逆向

网址&#xff1a;aHR0cHM6Ly93d3cub2tsaW5rLmNvbS96aC1oYW5zL2J0Yy90eC1saXN0L3BhZ2UvNAo 抓包分析&#xff0c;发现请求头有X-Apikey参数加密&#xff0c;其他表单和返回内容没有加密。 直接搜索关键字&#xff0c;X-Apikey&#xff0c;找到疑似加密位置&#xff0c;注意这里…

零基础学习Python(三)

1. 多重继承 一个子类可以继承多个父类&#xff0c;这与一些编程语言的规则不通。 如果多个父类中有同名的变量和方法&#xff0c;子类访问的顺序是按照继承时小括号里书写的顺序进行访问的。 可以用issubclass(B, A)方法判断B是否为A的子类。 2. 绑定 类中的方法通过参数s…

《TF2.x强化学习手册》P59-P65-SARSA-Q-learning

文章目录 实现SARSA算法和对应的强化学习智能体前期准备实现步骤工作原理初始化算法流程 构建基于Q学习的智能体前期准备实现步骤工作原理SARSA 算法的收敛性&#xff1a;SARSA 适合在线学习和真实系统&#xff1a;Q 学习算法的适用性&#xff1a; 实现SARSA算法和对应的强化学…