STM32之CAN通讯(十一)

STM32F407 系列文章 - CAN通讯(十一)


目录

前言

一、CAN

二、CAN驱动电路

三、CAN软件设计

1.CAN状态初始化

2.头文件相关定义

3.接收中断服务函数

4.用户层使用

1.用户层相关定义

2.发送数据

3.接收数据

1.查询方式处理

2.中断方式处理

3.总结

4.其它功能函数

总结


前言

一般STM32F407芯片都会自带2路CAN接口,分别为CAN1和CAN2,其通讯速度高达1Mb/s,每个CAN总线发送端具备三个发送邮箱,用来区别发送优先级,接收端具备两个具有三级深度的接收 FIFO,用来存储数据。一般从407芯片端口输出的CAN信号抗干扰性比较差,不足以保证通讯的稳定性和可靠性,这时我们就需要通过添加驱动电路,可以增强信号的驱动能力,确保信号在传输过程中不受干扰或衰减,从而提高通讯的稳定性和可靠性。一般市场上所卖的板子都带这一功能的,因此要实现CAN总线通讯功能,需准备STM32F407开发板一块和CAN-Tool分析仪工具一个。


一、CAN

控制器局域网总线(CAN,Controller Area Network)是ISO国际标准化的串行通信协议总线,使用双绞线来传输信号,具有高性能、高可靠性和易于扩展的特点,广泛应用于汽车、工业控制、机器人控制等领域,是世界上应用最广泛的现场总线之一。‌CAN总线协议_百度百科 (baidu.com)

CAN通讯是一种多主机串行异步通信总线,允许网络中的各个节点(设备)进行无中心控制的通信。每个节点都可以在总线上发送报文,当多个节点同时发送报文时,CAN总线会使用仲裁机制决定哪个报文优先发送,优先级高的报文会先发送,低优先级的报文则会停止发送。CAN总线的通信过程分为发送报文、仲裁机制、数据传输和错误检测与处理四个阶段。

要了解更为详细的CAN总线协议及其报文构成,可参考CAN总线通信协议-CSDN博客,讲的挺全面的。

二、CAN驱动电路

STM32单片机在进行CAN通讯时加驱动电路是为了增强信号传输能力、提供总线保护以及满足CAN总线物理层规范。这些措施有助于提高通讯的稳定性和可靠性,确保单片机与CAN总线上的其他设备之间的正常通讯。下面提供一款国产驱动芯片SIT1050,详细的芯片参数及引脚特性参考其datasheet。

SIT1050是一款应用于CAN协议控制器和物理总线之间的接口芯片,可应用于卡车、公交、小汽车、工业控制等领域,速率可达到1Mbps,具有在总线与CAN协议控制器之间进行差分信号传输的能力,设计电路原理如下。

三、CAN软件设计

CAN软件包括底层代码和用户层代码,底层代码主要完成CAN状态的初始化工作,主要涉及到对底层硬件引脚、时钟、中断的定义及调用;用户层代码主要完成对CAN总线上数据消息的解析和处理。

关于底层代码的实现可以通过调用HAL官方库文件,或者在可视化工具STM32CubeMX上配置,然后一键画生成底层代码。不管那种方式其实都是在以CAN_TypeDef *结构完成CAN寄存器的配置,在stm32f407xx.h文件上可查看。

1.CAN状态初始化

通过函数can1_init()完成,主要为CAN1的设置,包括波特率、工作模式设置、底层驱动配置( 包括引脚配置、时钟配置、中断配置)、过滤器设置、CAN总线外围设备、CAN中断使能设置等等,该函数被main()函数调用。代码中有详细的介绍,代码如下(示例):

CAN_HandleTypeDef hcan1;                /* CAN1句柄 */
CAN_HandleTypeDef hcan2;                /* CAN2句柄 */     /*** @brief       CAN初始化*  @note       Prescaler    : 重新同步跳跃时间单元.范围: 1~3;*              TimeSeg2    : 时间段2的时间单元.范围: 1~8;*              TimeSeg1    : 时间段1的时间单元.范围: 1~16;*              Prescaler     : 波特率分频器.范围: 1~1024;*              以上4个参数, 在函数内部会减1, 所以, 任何一个参数都不能等于0*              CAN挂在APB1上面, 其输入时钟频率为 Fpclk1 = PCLK1 = 42Mhz*              tq     = Prescaler * tpclk1;*              波特率 = Fpclk1 / ((TimeSeg1 + TimeSeg2 + 1) * Prescaler);*              已知42M时钟和500Kbps要求, 根据波特率公式*              配置TimeSeg1 = 7, TimeSeg2 = 6 , 为Prescaler = 6*              得出CAN波特率为: 42M / ((6 + 7 + 1) * 6) = 500Kbps** @param       mode    : CAN_MODE_NORMAL,  普通模式;CAN_MODE_LOOPBACK,回环模式;* @retval      0,  初始化成功; 其他, 初始化失败;*/
uint8_t can1_init(uint32_t mode)
{/*** 1.完成CAN1的波特率和模式设置*    这里也可以通过外部选择波特率配置can1_init(uint32_t bps, uint32_t mode)*    case 125:*    case 500:*    case 1000:***/hcan1.Instance = CAN1;hcan1.Init.Prescaler = 6;                 /* 分频系数(Fdiv)为Prescaler+1 */hcan1.Init.Mode = mode;                   /* 模式设置 */hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;   /* 重新同步跳跃宽度为SyncJumpWidth+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ */hcan1.Init.TimeSeg1 = CAN_BS1_7TQ;        /* 范围CAN_BS1_1TQ~CAN_BS1_16TQ */hcan1.Init.TimeSeg2 = CAN_BS2_6TQ;        /* 范围CAN_BS2_1TQ~CAN_BS2_8TQ */hcan1.Init.TimeTriggeredMode = DISABLE;   /* 非时间触发通信模式 */hcan1.Init.AutoBusOff = ENABLE;           /* 软件自动离线管理 */hcan1.Init.AutoWakeUp = DISABLE;          /* 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) */hcan1.Init.AutoRetransmission = ENABLE;   /* 禁止报文自动传送 */hcan1.Init.ReceiveFifoLocked = DISABLE;   /* 报文不锁定,新的覆盖旧的 */hcan1.Init.TransmitFifoPriority = ENABLE; /* 优先级由报文标识符决定 */// 2.完成CAN1的底层驱动配置 包括引脚配置、时钟配置、中断配置if (HAL_CAN_Init(&hcan1) != HAL_OK){Error_Handler();return 1;}// 3.完成ID号为1#设备的过滤器设置CAN_Filter_Config(&hcan1, 1);// 4.启动CAN1总线外围设备
#if CAN1_iSOpenif(HAL_CAN_Start(&hcan1)!=HAL_OK) {Error_Handler();return 1;}// 5.使能CAN1中断elseEnable_CAN1_Interrupts();
#endifreturn 0;
}

上面can1_init()函数包含了HAL_CAN_Init()、CAN_Filter_Config()、HAL_CAN_Start()、Enable_CAN1_Interrupts(),这4个函数分别完成如下功能:

  • HAL_CAN_Init(),此函数为HAL库函数,主要是调用HAL_CAN_MspInit()函数,以完成CAN1的底层驱动配置 包括引脚配置、时钟配置、中断配置。函数HAL_CAN_MspInit()同样为HAL库函数,但是其被定义为若函数,可以用来被重写的,代码如下(示例)。
/*** @brief       CAN底层驱动,引脚配置,时钟配置,中断配置此函数会被HAL_CAN_Init()调用* @param       hcan:CAN句柄* @retval      无*/
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{GPIO_InitTypeDef gpio_init_struct = {0};if (CAN1 == hcan->Instance){/* CAN1 clock enable */__HAL_RCC_CAN1_CLK_ENABLE();    /* 使能CAN1时钟 *//**CAN1 GPIO ConfigurationPA11     ------> CAN1_RXPA12     ------> CAN1_TX*/gpio_init_struct.Pin = GPIO_PIN_11|GPIO_PIN_12;gpio_init_struct.Mode = GPIO_MODE_AF_PP;gpio_init_struct.Pull = GPIO_PULLUP;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;gpio_init_struct.Alternate = GPIO_AF9_CAN1;HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* CAN1_RX和CAN1_TX脚 模式设置 *//* CAN1 interrupt Init */HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);}else if(CAN2 == hcan->Instance){/* CAN2 clock enable */__HAL_RCC_CAN2_CLK_ENABLE();    /* 使能CAN2时钟 *//**CAN2 GPIO ConfigurationPB12     ------> CAN2_RXPB13     ------> CAN2_TX*/gpio_init_struct.Pin = GPIO_PIN_12|GPIO_PIN_13;gpio_init_struct.Mode = GPIO_MODE_AF_PP;gpio_init_struct.Pull = GPIO_NOPULL;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;gpio_init_struct.Alternate = GPIO_AF9_CAN2;HAL_GPIO_Init(GPIOB, &gpio_init_struct); /* CAN2_RX和CAN2_TX脚 模式设置 *//* CAN2 interrupt Init */HAL_NVIC_SetPriority(CAN2_RX0_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN2_RX0_IRQn);HAL_NVIC_SetPriority(CAN2_RX1_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN2_RX1_IRQn);}
}
  • CAN_Filter_Config(),此函数完成CAN设备的过滤器设置,具体含义见代码中说明,代码如下(示例)。
/*
过滤的基本说明:
1 寄存器配置CAN_ID STID[10:3] STID[2:0]EXID[17:13]  EXID[12:5]  EXID[4:0]|IDE|RTR|0ID      [31:24]        [23:16]		     [15:8]			[7:0]MASK    [31:24]        [23:16]		     [15:8]			[7:0]STID 基本位11位 EXID扩展位18位 IDE扩展帧标识1位 RTR远程帧标识1位
2 掩码方式过滤的原则在掩码方式下,掩码寄存器某位为1表示接收到的帧ID对应的位必须与标识符寄存器对应的位相同IDE为扩展帧(CAN_ID_EXT,4),RTR为数据帧(CAN_RTR_DATA,0),掩码为1<<1|1<<2|0 = 6
3 应用实现仅处理扩展帧和数据帧,对标准帧、远程帧均不处理;使用FIFO0接收发送至本控制器的CAN消息;使用FIFO1接收其他控制器和单机的CAN消息;ID1单机需要接收全部CAN消息并进行转发,中间各单机仅接收并处理本机消息。
4 ID识别控制器接收消息的ID:xx xxxxx xxxxxx YYYYYY xx xxxxxxxx掩码:(0xFC00<<3)|0x6
*/
void CAN_Filter_Config(CAN_HandleTypeDef *hcan, uint8_t deviceID)
{CAN_FilterTypeDef filter;uint32_t filterID = (((uint32_t)deviceID<<10)<<3)|CAN_ID_EXT|CAN_RTR_DATA;uint32_t maskID = (0xFC00<<3)|(CAN_ID_EXT|CAN_RTR_DATA);//0x6;// 配置CAN过滤器filter.FilterMode = CAN_FILTERMODE_IDMASK;	   //选择标识符掩码模式(指示标识符的哪些位“必须匹配”)filter.FilterScale = CAN_FILTERSCALE_32BIT;	   //选择32位模式filter.FilterActivation = CAN_FILTER_ENABLE;	 //激活筛选器filter.SlaveStartFilterBank = 14;// FIFO0 筛选本机CAN消息filter.FilterBank = 0;						             //FilterBank=0 筛选器组编号(共28个可配置且可调整的筛选器组)filter.FilterIdHigh = filterID>>16;			       //32位标识符filter.FilterIdLow = filterID&0xFFFF;filter.FilterMaskIdHigh = maskID>>16;		       //32位掩码filter.FilterMaskIdLow = maskID&0xFFFF;filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;//FilterBank=0 关联到FIFO0if (CAN1 == hcan->Instance)                    //选择CAN1过滤器设置{if(HAL_CAN_ConfigFilter(&hcan1, &filter) != HAL_OK)Error_Handler();}else if (CAN2 == hcan->Instance)              //选择CAN2过滤器设置{filter.FilterBank = 14;if(HAL_CAN_ConfigFilter(&hcan2, &filter) != HAL_OK)Error_Handler();}/*** 特殊设置 *    FIFO1过滤器设置 仅ID1单机收其他单机的CAN消息***/if(DEVICEID == deviceID ){filter.FilterBank = 1;						            //过滤器编号filter.FilterIdHigh = 0x0000;				          //32位IDfilter.FilterIdLow = CAN_ID_EXT|CAN_RTR_DATA;filter.FilterMaskIdHigh = 0x0000;			        //32位MASKfilter.FilterMaskIdLow = CAN_ID_EXT|CAN_RTR_DATA;//0x6;filter.FilterFIFOAssignment=CAN_FILTER_FIFO1; //关联到FIFO1if (CAN1 == hcan->Instance)                   //选择CAN1过滤器设置{if(HAL_CAN_ConfigFilter(&hcan1, &filter) != HAL_OK)Error_Handler();}else if (CAN2 == hcan->Instance)              //选择CAN2过滤器设置{filter.FilterBank = 15;if(HAL_CAN_ConfigFilter(&hcan2, &filter) != HAL_OK)Error_Handler();}}/*** 特殊设置*    为适应标准帧,增加过滤器***/filterID = CAN_ID_STD|CAN_RTR_DATA;   //		?(0x0580UL<<21)|filter.FilterBank = 2;						    //过滤器编号filter.FilterIdHigh = 0x0000;				  //32位IDfilter.FilterIdLow = CAN_ID_STD|CAN_RTR_DATA;filter.FilterMaskIdHigh = 0x0000;			//32位MASKfilter.FilterMaskIdLow = CAN_ID_STD|CAN_RTR_DATA;filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;//关联到FIFO0if (CAN1 == hcan->Instance)                   //选择CAN1过滤器设置{if(HAL_CAN_ConfigFilter(&hcan1, &filter) != HAL_OK)Error_Handler();}
}
  • HAL_CAN_Start(),此函数主要完成开启CAN总线外围设备状态,该函数为HAL库函数,具体说明见官方说明,此处不提供代码。
  • Enable_CAN1_Interrupts(),完成CAN接收中断使能,以及FIFO和中断模式选择,具体含义见代码中说明,代码如下(示例)。
/*** @brief       使能CAN1接收中断 以及FIFO和中断模式选择* @note        根据STM官方手册F407每个CAN接收端具备两个具有三级深度的接收FIFO*              分别为FIFO0和FIFO1,每个接收中断又可以选择不同的中断模式,具体为*              CAN_IT_RX_FIFO0_MSG_PENDING模式:有消息就触发中断*              CAN_IT_RX_FIFO0_FULL模式:三级接收FIFO全满时触发中断*              CAN_IT_RX_FIFO0_OVERRUN:超出时就触发中断* @retval      一般推荐pending模式中断*/
void Enable_CAN1_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpenHAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
void Enable_CAN2_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpen	HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
/*** @brief       关闭CAN1接收中断* @retval*/
void Disable_CAN1_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_DeactivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpenHAL_CAN_DeactivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
void Disable_CAN2_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_DeactivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpenHAL_CAN_DeactivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}

该CAN状态初始化函数can1_init()一般在main()函数开始被调用,,代码如下(示例)。

int main(void)
{/* 略.....初始化设置代码 */can1_init(CAN_MODE_LOOPBACK);  /* CAN1初始化, 环回模式, 波特率500Kbps */while (1) {/* CAN消息解析处理 */   }
}

2.头文件相关定义

上述函数相关头文件定义,代码如下(示例):

#ifndef __CAN_H
#define __CAN_H#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"extern CAN_HandleTypeDef hcan1;
extern CAN_HandleTypeDef hcan2;
#define DEVICEID 1
#define CAN1_iSOpen 1
#define CAN2_iSOpen 0
#define RXFifo0_iSOpen 1
#define RXFifo1_iSOpen 1
#define isUserDefined 0
/******************************************************************************************/
/* CAN接收中断使能 */
#if isUserDefined
#define PRO_CAN1_RX0_IRQHandler  CAN1_RX0_IRQHandler
#define PRO_CAN1_RX1_IRQHandler  CAN1_RX1_IRQHandler
#define PRO_CAN2_RX0_IRQHandler  CAN2_RX0_IRQHandler
#define PRO_CAN2_RX1_IRQHandler  CAN2_RX1_IRQHandler
#else
void CAN1_RX0_IRQHandler(void); //ISR函数
void CAN1_RX1_IRQHandler(void); //ISR函数
void CAN2_RX0_IRQHandler(void); //ISR函数
void CAN2_RX1_IRQHandler(void); //ISR函数
#endif/* 函数声明 */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf);                                     /* CAN接收数据, 查询 */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len);                           /* CAN发送数据 */
uint8_t can1_init(uint32_t mode);                                                        /* CAN初始化 */
void Error_Handler(void);
void Recv_CAN_Msgs(CAN_HandleTypeDef*, uint32_t);
void CAN_Filter_Config(CAN_HandleTypeDef*, uint8_t);
void Enable_CAN1_Interrupts(void);
void Enable_CAN2_Interrupts(void);
void Disable_CAN1_Interrupts(void);
void Disable_CAN2_Interrupts(void);
#endif

3.接收中断服务函数

处理中断服务例程(Interrupt Service Routine, ISR)函数,它们由硬件中断触发自动执行。具体实现流程为:当处理器的CAN1接口的RX0缓冲区接收到数据时,硬件会触发一个中断;CPU响应这个中断,跳转到CAN1_RX0_IRQHandler这个中断服务例程的地址开始执行代码;这个中断服务例程通常负责读取接收到的数据,处理这些数据(比如,更新状态变量,发送数据到其他模块等),然后返回。

在startup_stm32f407xx.s文件(该文件每个项目工程中都有,主要作用为设置初始SP、设置初始处理器、设置带有异常ISR地址的向量表条目、调用main函数。)上可以看到代码如下(示例):

; Vector Table Mapped to Address 0 at ResetAREA    RESET, DATA, READONLY
__Vectors       DCD     __initial_sp               ; Top of StackDCD     Reset_Handler              ; Reset HandlerDCD     NMI_Handler                ; NMI Handler; External Interrupts                DCD     CAN1_RX0_IRQHandler               ; CAN1 RX0                                               DCD     CAN1_RX1_IRQHandler               ; CAN1 RX1 DCD     CAN2_RX0_IRQHandler               ; CAN2 RX0                                               DCD     CAN2_RX1_IRQHandler               ; CAN2 RX1 
__Vectors_End 

DCD CAN1_RX0_IRQHandler 这行代码是在汇编语言中使用的,用于定义一个数据常量。在这个上下文中,它定义了一个标签(或者说是一个内存地址),该标签指向 CAN1_RX0_IRQHandler 这个中断服务例程的地址。

这里介绍下ISR,通常有以下特点:

  • 它们必须尽可能快地执行,以减少中断延迟并避免阻塞其他中断的处理。
  • 它们通常访问特定的硬件寄存器来读取中断状态、清除中断标志,并处理中断引起的事件。
  • 在多任务环境或实时操作系统(RTOS)中,ISR可能负责设置标志或发送消息给任务,以便在ISR之外处理更耗时的任务。
  • 在编写ISR时,开发者需要确保它们遵循特定的硬件和编译器要求,比如使用特定的中断向量表入口点名称(在这个例子中是CAN1_RX0_IRQHandler),以及可能需要在函数开始和结束时添加特定的汇编指令或内联代码来保存和恢复CPU寄存器状态。

在上面第二小节-头文件相关定义中,可以看到对中断服务例程函数的定义或宏替换,代码如下(重写一下):

#define isUserDefined 0#if isUserDefined
#define PRO_CAN1_RX0_IRQHandler  CAN1_RX0_IRQHandler
#define PRO_CAN1_RX1_IRQHandler  CAN1_RX1_IRQHandler
#define PRO_CAN2_RX0_IRQHandler  CAN2_RX0_IRQHandler
#define PRO_CAN2_RX1_IRQHandler  CAN2_RX1_IRQHandler
#else
void CAN1_RX0_IRQHandler(void); //ISR函数
void CAN1_RX1_IRQHandler(void); //ISR函数
void CAN2_RX0_IRQHandler(void); //ISR函数
void CAN2_RX1_IRQHandler(void); //ISR函数
#endif

相关函数的实现如下。这里需要说明一下,如果你是通过STM32CubeMX生成的代码,相应的ISR函数会在stm32f4xx_it.c文件(此文件为所有异常处理程序和外围设备中断服务程序)上实现。

/*** @brief  弱函数 可被重写*/
__weak void Recv_CAN_Msgs(CAN_HandleTypeDef *hcan, uint32_t RxFifo)
{UNUSED(hcan);/* NOTE : This function Should not be modified, when the callback is needed,the Recv_CAN_Msgs could be implemented in the user file*/
}
/*** @brief       CAN_RX中断服务函数 引用中断处理函数*   @note      处理CAN FIFO0的接收中断* @param       无* @retval      无*/
void PRO_CAN1_RX0_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(&hcan1, CAN_RX_FIFO0);
#elseHAL_CAN_IRQHandler(&hcan1);
#endif
}
void PRO_CAN1_RX1_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(&hcan1, CAN_RX_FIFO1);
#elseHAL_CAN_IRQHandler(&hcan1);
#endif
}
void PRO_CAN2_RX0_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(&hcan2, CAN_RX_FIFO0);
#elseHAL_CAN_IRQHandler(&hcan2);
#endif
}
void PRO_CAN2_RX1_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(&hcan2, CAN_RX_FIFO1);
#elseHAL_CAN_IRQHandler(&hcan2);
#endif
}#if !isUserDefined
/*** @brief  处理中断服务例程(ISR)函数 它们由硬件中断触发自动执行*  @note  This function handles CAN1 CAN1 RX interrupts.  */
void CAN1_RX0_IRQHandler(void)
{PRO_CAN1_RX0_IRQHandler();
}
void CAN1_RX1_IRQHandler(void)
{PRO_CAN1_RX1_IRQHandler();
}
void CAN2_RX0_IRQHandler(void)
{PRO_CAN2_RX0_IRQHandler();
}
void CAN2_RX1_IRQHandler(void)
{PRO_CAN2_RX1_IRQHandler();
}
#endifvoid Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state *//* USER CODE END Error_Handler_Debug */
}

4.用户层使用

用户的使用主要为发送数据和接收数据两个动作,这两个动作中均包含CAN数据的处理与解析过程。在讲解这两个动作前,先完成其相关变量、功能函数、引用头文件的定义,在头文件上完成。

1.用户层相关定义

发送数据和接收数据的相关函数头文件定义,代码如下(示例):

#ifndef __CAN_USER_H
#define __CAN_USER_H#ifdef __cplusplus
extern "C"
{
#endif#include "string.h"
#include "./BSP/CAN/can.h"
#include "./BSP/CAN/lib_array.h"
extern Block_Circle_Array ProcCANArray;
extern Block_Circle_Array CANSendArray;
extern Block_Circle_Array g_PushCANArray;
typedef union _Union_Bit64 {uint64_t u64;int64_t i64;double f64;uint32_t u32[2];int32_t i32[2];float f32[2];uint16_t u16[4];int16_t i16[4];uint8_t b[8];
}UNION_BIT64;typedef struct
{CAN_EXT_ID ID;	  // 消息IDuint8_t Len;	  // 消息数据长度(字节)UNION_BIT64 Data; // 消息数据
}__attribute__((packed)) CAN_Message;
typedef enum
{STD_CAN_MSG = 4,EXT_CAN_MSG = 0
}CAN_Msg_Type;#define CAN_MSG_TYPE_FILTER 0x4
typedef enum
{CAN1_TIME = 1,CAN2_TIME,UART1_TIME,UART2_TIME,UART3_TIME,IIC_TIME,NET_TIME
}DeviceComTimeIndex; // 设备通讯时间索引typedef enum
{CMD1 = 1,CMD2,CMD3,CMD4
}Info_Cmd;
typedef enum
{SRC1 = 1,SRC2,SRC3,SRC4
}Info_Src;
typedef enum
{DES1 = 1,DES2,DES3,DES4
}Info_Des;typedef enum
{ID1 = 0x1,ID2 = 0x2,ID3 = 0x3,ID4 = 0x4,UNKNOWN_ID = 0x0 // 未知节点
}Device_ID;#define CombCMD(cmd, src, des) (((cmd) << 12) | ((src) << 6) | des) // 根据消息命令,源节点和目标节点的ID组合成实际可辨别的命令
typedef enum
{ID1_BUS_CMD = CombCMD(CMD1, SRC1, DES1),ID2_BUS_CMD = CombCMD(CMD2, SRC2, DES2),ID3_BUS_CMD = CombCMD(CMD3, SRC3, DES3),ID4_BUS_CMD = CombCMD(CMD4, SRC4, DES4),
}CAN_Msg_Cmds;
void User_CAN_Init(void);
void Reset_CAN_Recv_Array(void);
uint32_t Create_Ext_CAN_Msg_ID(Bus_Pri_ID pri, uint8_t mid, Device_ID src, Device_ID des, CAN_Bus_ID bus, uint8_t index);
void Push_CAN_Send_Array(CAN_Bus_ID CANChannel, CAN_Message *msg);
void CAN_Data_Process(void);
void CAN_Msg_process(CAN_Message *msg);
#ifdef __cplusplus
}
#endif#endif

2.发送数据

发送数据主要在Send_CAN_Msgs()函数上完成,一般我们使用的环境比较复杂,使用单机设备较多、或者数据量较大时,为保证数据的及时处理和数据的完整性,这时我们需要建立一个缓存块,用来存放数据,实现代码如下(示例):

//发送CAN消息
void Send_CAN_Msgs(void)
{CAN_TxHeaderTypeDef msgHead;  /* 发送参数句柄 */CAN_Message msg;uint32_t txMailbox;
#if CAN1_iSOpenuint8_t CAN1FreeBoxs = 0;uint8_t CAN1Msgs = BlockCircleArray_GetBlockCount(&CANSendArray); //缓存块if(CAN1Msgs > 0) //有缓存的数据{CAN1FreeBoxs = HAL_CAN_GetTxMailboxesFreeLevel(&hcan1);if(0 < CAN1FreeBoxs)  // 有空邮箱{BlockCircleArray_Get(&CANSendArray, (uint8_t*)&msg);Set_CAN_TxHeader(&msgHead, msg);if(HAL_OK == HAL_CAN_AddTxMessage(&hcan1, &msgHead, msg.Data.b, &txMailbox)){//发送成功在缓存块上删除该消息,不成功时保留BlockCircleArray_Slide(&CANSendArray); //Delay_us(10);}}}
#endif
//是否开放CAN2
#if CAN2_iSOpenuint8_t CAN2FreeBoxs = 0;uint8_t CAN2Msgs = BlockCircleArray_GetBlockCount(&CANSendArray);if(CAN2Msgs > 0){CAN2FreeBoxs = HAL_CAN_GetTxMailboxesFreeLevel(&hcan2);if(0 < CAN2FreeBoxs){BlockCircleArray_Get(&CANSendArray, (uint8_t*)&msg);Set_CAN_TxHeader(&msgHead, msg);if(HAL_OK == HAL_CAN_AddTxMessage(&hcan2, &msgHead,msg.Data.b, &txMailbox)){BlockCircleArray_Slide(&CANSendArray);//Delay_us(5);}}}
#endif
//测试发送一组数据
#if 0
/*** @brief       测试CAN 发送一组数据*   @note      发送格式固定为: 标准ID, 数据帧* @param       id      : 标准ID(11位)* @param       msg     : 数据指针* @param       len     : 数据长度*/uint32_t id = 0x11;uint8_t len = 8;uint8_t msgs[len];uint16_t t = 0;txMailbox = CAN_TX_MAILBOX0;msgHead.StdId = id;         /* 标准标识符 */msgHead.ExtId = id;         /* 扩展标识符(29位) */msgHead.IDE = CAN_ID_STD;   /* 使用标准帧 */msgHead.RTR = CAN_RTR_DATA; /* 数据帧 */msgHead.DLC = len;if (HAL_CAN_AddTxMessage(&hcan1, &msgHead, msgs, &txMailbox) != HAL_OK) /* 发送消息 */{return;}while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) != 3)   /* 等待发送完成,所有邮箱为空 */{t++;if (t > 0xFFF) {HAL_CAN_AbortTxRequest(&hcan1, txMailbox);     /* 超时,直接中止邮箱的发送请求 */return;}}
#endif
}

3.接收数据

CAN数据的接收,一般有两种实现方式:一种时通过中断接收并处理消息,另一种是通过查询的方式实现。这里推荐采用中断方式实现,这种方式高效、便捷,工业上采取这种方式实现;查询的方式,适用于功能简单、且数据量少的情况使用。

1.查询方式处理

采用查询方式实现,一般是在主函数的主循环中,通过while(1)中,不断地查询是否接收导数据,因此此方式缺点就在这里,需保证主循环中无其他重大耗时的功能,实现代码如下(示例)。

/*** @brief       CAN 接收数据查询*   @note      接收数据格式固定为: 标准ID, 数据帧* @param       id      : 要查询的 标准ID(11位)* @param       buf     : 数据缓存区* @retval      接收结果*   @arg       0   , 无数据被接收到;*   @arg       其他, 接收的数据长度*/
uint8_t can_receive_msg(uint32_t id, uint8_t *buf)
{if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) == 0)     /* 没有接收到数据 */{return 0;}CAN_RxHeaderTypeDef g_canx_rxheader;    /* 接收参数句柄 */if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &g_canx_rxheader, buf) != HAL_OK)  /* 读取数据 */{return 0;}if (g_canx_rxheader.StdId!= id || g_canx_rxheader.IDE != CAN_ID_STD || g_canx_rxheader.RTR != CAN_RTR_DATA)       /* 接收到的ID不对 / 不是标准帧 / 不是数据帧 */{return 0;    }return g_canx_rxheader.DLC;
}

在main()函数实现如下。

int main(void)
{uint32_t id = 0x12;uint8_t canbuf[8];uint8_t rxlen = 0;/* 略.....初始化设置代码 */while (1) {rxlen = can_receive_msg(id, canbuf);  /* CANID=0x12, 接收数据查询 */if (rxlen) /* 是否接收到有数据 */ {/* 解析处理 */   }}
}
2.中断方式处理

前面第三小节已经完成对CAN接收的中断服务函数定义,这里继续在上面实现解析处理功能。在前面的HAL库函数HAL_CAN_IRQHandler()里面,会调用HAL_CAN_RxFifo0MsgPendingCallback()函数,该函数为若函数,已被重写,调用Recv_CAN_Msgs()和Pre_Process_CAN_Msgs()完成数据解析功能,相关函数解释如下:

  • HAL_CAN_RxFifo0MsgPendingCallback(),此函数为HAL库函数,但是其被定义为若函数,可以用来被重写的。重写调用Recv_CAN_Msgs()函数,以完成数据解析;
  • Recv_CAN_Msgs(),关键数据接收处理函数,完成CAN消息的帧头以及数据内容接收,并判断来帧类型、帧来源等信息,该函数也可以不通过被HAL_CAN_RxFifo0MsgPendingCallback()函数调用,自行处理,直接被中断服务例程ISR函数调用,具体代码实现方式见第三小节-接收中断服务函数,将宏define isUserDefined定义为1,即可。
  • Pre_Process_CAN_Msgs(),消息预处理,放入缓存队列。

代码如下(示例)。

// 消息预处理,放入缓存队列
void Pre_Process_CAN_Msgs(CAN_Message* prcMsg)
{if(prcMsg != NULL){if(Check_New_CAN_Msg(&RecvIDList, g_1msTick, prcMsg->ID))	// 检查是否是新消息,防止CAN1、CAN2上的重复消息{// 来自于SRC1的针对本机的控制命令,待进一步处理信息if(prcMsg->ID.s.src == SRC1)BlockCircleArray_Push(&ProcCANArray, (uint8_t*)prcMsg);}		}
}void Recv_CAN_Msgs(CAN_HandleTypeDef *hcan, uint32_t RxFifo)
{CAN_RxHeaderTypeDef rxHeader;CAN_Message rcvMsg = {0};CAN_Msg_Type msgType;while(HAL_CAN_GetRxFifoFillLevel(hcan, RxFifo)){// 获得接收到的数据头和数据if (HAL_CAN_GetRxMessage(hcan, RxFifo, &rxHeader, rcvMsg.Data.b) == HAL_OK){if(rxHeader.IDE == CAN_ID_EXT) // 扩展帧{rcvMsg.ID.id = rxHeader.ExtId;msgType = EXT_CAN_MSG;}else // 标准帧{rcvMsg.ID.id = rxHeader.StdId;msgType = STD_CAN_MSG;}rcvMsg.Len = rxHeader.DLC;if(hcan == &hcan1) // 通过地址判断是CAN1地址区接受的数据还是CAN2{// 使用预留的数据区保存当前消息的接收总线通道号rcvMsg.ID.s.res = CAN1_BUS | msgType;g_DeviceComTime[CAN1_TIME] = g_1msTick;}else{rcvMsg.ID.s.res = CAN2_BUS | msgType;g_DeviceComTime[CAN2_TIME] = g_1msTick;}Pre_Process_CAN_Msgs(&rcvMsg);			}else{break;}}
}/*** @brief  弱函数 已被重写*  @note  pending callback 接收中断模式 *         处理CAN句柄上接收FIFO0的消息 该函数被HAL_CAN_IRQHandler()调用*/
// FIFO0收到的是本机信息
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{Recv_CAN_Msgs(hcan, CAN_RX_FIFO0);
}
// FIFO1收到的是其他单机信息
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{Recv_CAN_Msgs(hcan, CAN_RX_FIFO1);
}

 在main()函数实现如下。

int main(void)
{User_CAN_Init();/* 略.....初始化设置代码 */while (1) {/*** 其它功能 ***/CAN_Data_Process();}
}
3.总结

从上面对CAN数据接收的这两种实现方式,可以看出不管是哪一种方式,其底层都是查询调用can的FIFO缓存是否有数据,不同的是,一种在中断里面查看,一种是在主循环里面查看。

4.其它功能函数

其它功能主要是完成一些参数变量的初始化设置,和对CAN消息缓存块的解析处理,代码如下(示例)。

#include "./BSP/CAN/can_user.h"/*** 完成参数定义 ***/
void CAN_Arrays_Init(void)
{BlockCircleArray_Init(&ProcCANArray, (uint8_t*)ProcCANMsgs, CAN_MSG_LEN, MAX_CAN_PROC_MSGS);BlockCircleArray_Init(&CANSendArray, (uint8_t*)CANSendMsgs, CAN_MSG_LEN, MAX_CAN_SEND_MSGS);BlockCircleArray_Init(&g_PushCANArray, (uint8_t*)PushCANMsgs, CAN_MSG_LEN, MAX_CAN_PUSH_MSGS);
}
void User_CAN_Init(void)
{CAN_Arrays_Init();
}
// 将待发送的测试信息压入CAN待发送缓存队列
void Push_CAN_Send_Array(CAN_Bus_ID CANChannel, CAN_Message* msg)
{
#if CAN1_iSOpenif(CANChannel == CAN1_BUS && msg != NULL)BlockCircleArray_Push(&CANSendArray, (uint8_t*)msg);
#endif	
#if CAN2_iSOpenelse if(CANChannel == CAN2_BUS && msg != NULL)BlockCircleArray_Push(&CANSendArray, (uint8_t*)msg);
#endif
}
/* CAN总线网络异常处理,尚未完成并验证
*  在系统初始化时设置了自动离线管理
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{uint32_t err = hcan->ErrorCode;// uint8_t status=0;// uint32_t canTSR = hcan->Instance->TSR;// uint32_t canABRQ = CAN_TSR_ABRQ0;    // 终止发送// uint32_t canTERR = CAN_TSR_TERR0;    // 发送失败// uint32_t canALST = CAN_TSR_ALST0;    // 仲裁失败switch(err){case HAL_CAN_ERROR_EWG:    // EWG error   break;case HAL_CAN_ERROR_EPV:    // EPV error break;case HAL_CAN_ERROR_BOF:    // BOF errorbreak;case HAL_CAN_ERROR_STF:    // Stuff errorbreak;case HAL_CAN_ERROR_FOR:    // Form errorbreak;case HAL_CAN_ERROR_ACK:    // Acknowledgment errorbreak;case HAL_CAN_ERROR_BR:    // Bit recessive break;case HAL_CAN_ERROR_BD:    // LEC dominant break;case HAL_CAN_ERROR_CRC:    // LEC transfer errorbreak;case HAL_CAN_ERROR_NONE:    // No errorbreak;default:break;}     hcan->ErrorCode = HAL_CAN_ERROR_NONE;
}*/
void Set_CAN_TxHeader(CAN_TxHeaderTypeDef* header, CAN_Message msg)
{// if(msg.ID.s.res == EXT_CAN_MSG)// 扩展帧// {header->ExtId = msg.ID.id;header->StdId = 0;header->IDE = CAN_ID_EXT;// }// else // 标准帧// {// 	header->StdId = msg.ID.id & 0xFFFF;// 	header->ExtId = 0;// 	header->IDE = CAN_ID_STD;// }header->RTR = CAN_RTR_DATA;header->DLC = msg.Len;	header->TransmitGlobalTime = DISABLE;// 只能设置为disable 	
}
uint32_t Create_Ext_CAN_Msg_ID(Bus_Pri_ID pri, uint8_t mid, Device_ID src, Device_ID des, CAN_Bus_ID bus, uint8_t index)
{CAN_EXT_ID id;id.s.res = EXT_CAN_MSG;id.s.pri = pri;id.s.mid = mid;id.s.src = src;id.s.des = des;id.s.bus = bus;id.s.index = index;return id.id;
}
// 单消息解析处理
void CAN_Msg_process(CAN_Message* msg)
{/*** 功能函数 ***/
}
// 多消息的处理
void CAN_Msgs_Process(void)
{/*** 设计思路在main函数调用CAN_Msg_process() ***/
}

5.主函数使用

上述完成对CAN接口底层代码和用户层代码的编写,这里实现其功能被主函数main()使用,代码如下(示例)。

/********************************************************************************* @file           : main.c* @brief          : Main program body*******************************************************************************/
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/CAN/can_user.h"int main(void)
{uint8_t key;uint8_t mode = 1; /* CAN工作模式: 0,普通模式; 1,环回模式 */HAL_Init();                             /* 初始化HAL库 */sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */delay_init(168);                        /* 延时初始化 */usart_init(115200);                     /* 串口初始化为115200 */led_init();                             /* 初始化LED */lcd_init();                             /* 初始化LCD */key_init();                             /* 初始化按键 */can1_init(CAN_MODE_LOOPBACK);           /* CAN初始化, 环回模式, 波特率500Kbps */User_CAN_Init();//lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);//lcd_show_string(30, 70, 200, 16, 16, "CAN TEST", RED);while (1){/* 中断方式 包含发送数据和接收数据 */CAN_Data_Process();/* 查询方式 CAN ID = 0x12, 接收数据查询uint8_t rxlen = can_receive_msg(0x12, canbuf);  if (rxlen) { // 接收到有数据for (uint8_t i = 0; i < rxlen; i++) {// 处理数据 显示数据}}*/key = key_scan(0);	if (key == KEY1_PRES) {  /* KEY1_PRES按下, 改变CAN的工作模式 */mode = !mode;/* CAN初始化, 普通(0)/回环(1)模式, 波特率500Kbps */can1_init(mode ? CAN_MODE_LOOPBACK : CAN_MODE_NORMAL);if (mode == 0)  /* 普通模式, 需要2个开发板 */printf("Normal Mode");else           /* 回环模式,一个开发板就可以测试了. */printf("LoopBack Mode");Reset_CAN_Recv_Array();}delay_ms(1);}
}

总结

下面提供的代码,基于STM32F407ZGT芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。

相应的代码链接:单片机STM32F407-Case程序代码例程-CSDN文库

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

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

相关文章

添加系统级res资源包

//刚做完自定义res资源包的配置&#xff0c;这里做一下关于在配置过程中出现的问题和解决方法作一下记录。 资源的引用格式为&#xff1a; 包名&#xff1a;资源类型/资源名以framework资源为例&#xff1a; android:style/Theme.Holo.Light这次需要配置与framework同级的资…

LabVIEW计算机软件著作权

计算机软件著作权是指软件开发者对其创作的软件作品享有的法律保护权利&#xff0c;目的是防止他人未经授权复制、修改或传播该软件。软件著作权不仅包括软件的源代码&#xff0c;还包括文档、界面设计、功能模块、程序逻辑等内容。通过登记软件著作权&#xff0c;开发者可以获…

unity学习13:gameobject的组件component以及tag, layer 归类

目录 1 gameobject component 是unity的基础 1.1 类比 1.2 为什么要这么设计&#xff1f; 2 从空物体开始 2.1 创建2个物体 2.2 给 empty gameobject添加组件 3 各种组件和新建组件 3.1 点击 add component可以添加各种组件 3.2 新建组件 3.3 组件的操作 3.4 特别的…

Vue项目中的问题汇总(持续更新中)

1.vue 循环 span 标签产生了间隙 代码如下&#xff1a; <template><div class"box"><span v-for"(item,index) in items" ::key"index">{{ item }}</span><span>修改</span><span>删除</span>…

ffmpeg7.0 合并2个 aac 文件

ffmpeg7.0 将2个aac文件合并。 #include <stdio.h>// 之所以增加__cplusplus的宏定义&#xff0c;是为了同时兼容gcc编译器和g编译器 #ifdef __cplusplus extern "C" { #endif #include <libavformat/avformat.h> #include <libavcodec/avcodec.h>…

Midjourney 应用:框架总结

Midjourney 应用&#xff1a;框架总结 官方的模板很简单&#xff0c;分成四个部分&#xff1a; 主体细节 & 背景风格、媒介、艺术家参数 我的总结 其实按照官方模板写&#xff0c;你已经能超过 90% 的初学者&#xff0c;但根据我的实验&#xff0c;我细化了他们的模板的…

JVM实战—OOM的定位和解决

1.如何对系统的OOM异常进行监控和报警 (1)最佳的解决方案 最佳的OOM监控方案就是&#xff1a;建立一套监控平台&#xff0c;比如搭建Zabbix、Open-Falcon之类的监控平台。如果有监控平台&#xff0c;就可以接入系统异常的监控和报警&#xff0c;可以设置当系统出现OOM异常&…

JVM实战—13.OOM的生产案例

大纲 1.每秒仅上百请求的系统为何会OOM(RPC超时时间设置过长导致QPS翻几倍) 2.Jetty服务器的NIO机制如何导致堆外内存溢出(S区太小 禁NIO的显式GC) 3.一次微服务架构下的RPC调用引发的OOM故障排查实践(MAT案例) 4.一次没有WHERE条件的SQL语句引发的OOM问题排查实践(使用MA…

【银河麒麟高级服务器操作系统实例】tcp半链接数溢出分析及处理全过程

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://document.kylinos.cn 服务器环境以及配置 系统环境 物理机/虚拟机/云…

visual studio 自动调整代码格式的问题:

1.取消自动调整格式 2.如果是想让代码显得更紧凑&#xff0c;上面的不动&#xff0c;按这个来&#xff1a;

javaEE-网络原理-1初识

目录 一.网络发展史 1.独立模式 2.网络互联 二.局域网LAN 1.基于网线直连&#xff1a; 2.基于集线器组件&#xff1a; 3.基于交换机组件&#xff1a; 4.基于交换机和路由器组件 ​编辑 三、广域网WAN 四、网络通信基础 1.ip地址 2.端口号&#xff1a; 3.协议 4.五…

三维卷积( 3D CNN)

三维卷积&#xff08; 3D CNN&#xff09; 1.什么是三维卷积 1.1 三维卷积简介 二维卷积是在单通道的一帧图像上进行滑窗操作&#xff0c;输入是高度H宽度W的二维矩阵。 三维卷积输入多了深度C这个维度&#xff0c;输入是高度H宽度W深度C的三维矩阵。在卷积神经网络中&…

黄仁勋演讲总结(2种显卡,1个开源大模型,1个数据采集平台)

研发算力显卡RTX50系列&#xff0c;PC端显卡GB10&#xff0c;开源大模型Cosmos&#xff08;用于机器人和自动驾驶&#xff09;&#xff0c; Isaac GR00T&#xff08;人形机器人的数据采集平台&#xff09;。 新一代 RTX 50 系列显卡 RTX 50 系列 GPU&#xff0c;相对之前系列&a…

阿尔法linux开发板ping不通百度

我使用的阿尔法linux板子&#xff0c;发现按照《03【正点原子】I.MX6U网络环境TFTP&NFS搭建手册V1.3.2》一套操作下来&#xff0c;还是没办法实现板子上网。 我总结了下面方法&#xff0c;我如何实现联网和互ping通&#xff0c;大致总结下三步 一、pc端的wifi网络&#xf…

使用图像过滤器在 C# 中执行边缘检测、平滑、浮雕等

图像过滤器可让您对图像中的像素执行操作。这是一个相当大的示例,因此您可能需要花一些时间浏览代码。 在一种图像滤镜中,您有一个称为滤镜内核的值数组。对于图像中的每个像素,您将内核置于该像素的中心。然后将内核下的每个像素的值乘以相应的内核值。将它们相加,除以“…

数值分析速成复习笔记

请确保你有10hour的有效学习时间&#xff0c;保你拿90 证明部分 编程部分

如何快速上手一个鸿蒙工程

作为一名鸿蒙程序猿&#xff0c;当你换了一家公司&#xff0c;或者被交接了一个已有的业务。前辈在找你之前十分钟写了一个他都看不懂的交接文档&#xff0c;然后把一个鸿蒙工程交接给你了&#xff0c;说以后就是你负责了。之后几天你的状态大概就是下边这样的&#xff0c;一堆…

asammdf python库解析MF4文件(一)cut and filter

目录 cutfilter asammdf 是一个功能强大的 Python 库&#xff0c;专门用于处理汽车行业常用的 MDF&#xff08;Measured Data Format&#xff09;文件 这篇文章主要介绍mdf库的cut和filter函数 cut cut函数主要用于裁剪数据&#xff0c;比如你的MF4文件是一个100s的数据&…

性能测试01|性能测试理论

目录 一、性能测试概述 二、性能测试的分类 1、基准测试 2、负载测试 3、稳定性测试 4、压力测试 5、并发测试 三、性能测试的指标 1、响应时间 2、并发用户数 3、吞吐量 4、点击数 5、错误率 6、资源利用率 四、性能测试流程 1、性能需求分析 2、性能测试计划…

基于SpringBoot的斯诺克球馆预约购票管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…