STM32之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文库
相关文章:
STM32之CAN通讯(十一)
STM32F407 系列文章 - CAN通讯(十一) 目录 前言 一、CAN 二、CAN驱动电路 三、CAN软件设计 1.CAN状态初始化 2.头文件相关定义 3.接收中断服务函数 4.用户层使用 1.用户层相关定义 2.发送数据 3.接收数据 1.查询方式处理 2.中断方式处理 3…...
23.行号没有了怎么办 滚动条没有了怎么办 C#例子
新建了一个C#项目,发现行号没有了。 想把行号调出来,打开项目,选择工具>选项> 如下图,在文本编辑器的C#里有一个行号,打开就可以了 滚动条在这里:...
QT 下拉菜单设置参数 起始端口/结束端口/线程数量 端口扫描4
上篇文章QT实现 端口扫描暂停和继续功能 3-CSDN博客 双击 添加对话框类 界面设计 由于主体代码已经写完,只需要更改参数的获取即可 获取起始端口结束端口的输入 槽函数 给主界面类添加调用对话框类的功能 实现功能:点击菜单项可以弹出对话框窗体 增加槽…...
spark——RDD算子集合
目录 算子转换算子示例mapflatMapReduceByKeyfilterdistinctglomgroupBygroupByKeySortBysortByKeyunion交集intersection和差集subtractjoinpartitionBymapPartitionsample 行动算子示例ForeachPartitionForeachSaveAsTextFileCountByKeyReducefoldfirst、take、counttop、tak…...
【ArcGIS Pro二次开发实例教程】(1):图层的前置、后置
一、简介 此工具要实现的功能是:将内容框中当前选定的图层移到最顶层或最底层。 主要技术要点包括: 1、Config.daml文件设置(UI设置) 2、按钮的图片和位置设置 3、当前选定图层的获取 4、图层在内容列表中位置的获取和移动 …...
idea 运行 docker-compose 文件问题
我idea 连接远程docker 然后本地运行compose 文件出了问题 C:\Program Files\Docker\Docker\resources\bin\docker.exe compose -f D:\aproject\group-buy-market\suger_group_buy\docs\dev-ops\docker-compose-environment.yml -p dev-ops up -d正在准备 Docker SSH 环境…ti…...
深度学习中的正则化方法
最近看到了正则化的内容,发现自己对正则化的理解已经忘得差不多了,这里在整理一下,方便以后查阅。 深度学习中的正则化方法 1. L2 正则化(L2 Regularization)2. L1 正则化(L1 Regularization)3.…...
LInux单机安装Redis
1. 安装gee工具包 由于Redis是基于c语言编写的所以安装的时候需要先安装gee以及gcc的依赖,yum云用不了可以看一下这个 linux 替换yum源镜像_更换yum镜像源-CSDN博客 yum install -y gcc tcl 2. 添加redis的压缩包 3. 上传到Linux 上传到 /usr/local/src 目录、这个目录一般用于…...
kafka使用以及基于zookeeper集群搭建集群环境
一、环境介绍 zookeeper下载地址:https://zookeeper.apache.org/releases.html kafka下载地址:https://kafka.apache.org/downloads 192.168.142.129 apache-zookeeper-3.8.4-bin.tar.gz kafka_2.13-3.6.0.tgz 192.168.142.130 apache-zookee…...
深入理解 pytest_runtest_makereport:如何在 pytest 中自定义测试报告
pytest_runtest_makereport 是 pytest 系统中的一个钩子函数,它允许我们在测试执行时获取测试的报告信息。通过这个钩子,我们可以在测试运行时(无论是成功、失败还是跳过)对测试结果进一步处理,比如记录日志、添加自定…...
嵌入式技术之Linux(Ubuntu) 一
一、Linux入门 1.硬件和操作系统以及用户的关系 一个传感器,获得数据后,需要向服务器发送数据。传感器传数据给上位机。 上位机需要一个程序来接收数据,那么这个上位机是什么机器? 我们的笔记本电脑就可以当成上位机。 两个手…...
VB.NET CRC32 校验
在 VB.NET 中实现 CRC32 校验并在校验失败时退出程序,你可以按照以下步骤进行: 实现 CRC32 计算函数:首先,你需要一个函数来计算给定数据的 CRC32 值。 比较计算的 CRC32 值:然后,你需要将计算出的…...
iOS - 弱引用表(Weak Reference Table)
1. 基本数据结构 // 弱引用表的基本结构 struct weak_table_t {weak_entry_t *weak_entries; // 保存所有的弱引用对象size_t num_entries; // 当前存储的弱引用数量uintptr_t mask; // 哈希表大小掩码uintptr_t max_hash_displacement; /…...
Taro地图组件和小程序定位
在 Taro 中使用腾讯地图 1.首先在项目配置文件 project.config.json 中添加权限: {"permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序位置接口的效果展示"}} }2.在 app.config.ts 中配置&#x…...
汇编实现函数调用
x86_64 通过将函数参数存放在栈中的方式来实现参数传递。 # PURPOSE: Program to illustrate how functions work # This program will compute the value of # 2^3 5^2 ## Everything in the main program is stored in registers, # so the data section…...
C#—Task异步的常用方法及TaskFactory工厂类详解
Task异步的常用方法 C# 中的 Task 类是 System.Threading.Tasks 命名空间的一部分,用于表示异步操作。 以下是一些常用的 Task 类方法: 一、Task.Run(Action action): 此静态方法用于在后台运行一个新任务,并返回与该任务关联的 Task 实例…...
JAVA | 通过自定义注解与AOP防止接口重复提交
关注:CodingTechWork 引言 在Web应用开发中,特别是在处理表单提交或API调用时,可能会遇到用户因网络延迟、按钮多次点击等原因导致的重复提交问题。为了解决这一问题,通常的做法是在前端禁用提交按钮,或者在后端使用唯…...
从零手写实现redis(四)添加监听器
1、删除监听器 /*** 删除监听器接口** author binbin.hou* since 0.0.6* param <K> key* param <V> value*/ public interface ICacheRemoveListener<K,V> {/*** 监听* param context 上下文* since 0.0.6*/void listen(final ICacheRemoveListenerContext&…...
Spring Boot项目中使用单一动态SQL方法可能带来的问题
1. 查询计划缓存的影响 深入分析 数据库系统通常会对常量SQL语句进行编译并缓存其执行计划以提高性能。对于动态生成的SQL语句,由于每次构建的SQL字符串可能不同,这会导致查询计划无法被有效利用,从而需要重新解析、优化和编译,…...
51单片机——中断(重点)
学习51单片机的重点及难点主要有中断、定时器、串口等内容,这部分内容一定要认真掌握,这部分没有学好就不能说学会了51单片机 1、中断系统 1.1 概念 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的,中断功能的存在&#…...
MySQL insert or update方式性能比较
MySQL中,有如下两种方式,哪种方式比较好? 1、先使用enterprise_id字段查询数据表,如果表中存在记录,则更新记录;如果不存在,则插入记录; 2、使用“INSERT INTO XXX ON DUPLICATE K…...
Linux下常用命令
本文以笔记的形式记录Linux下常用命令。 注1:限于研究水平,阐述难免不当,欢迎批评指正。 注2:文章内容会不定期更新。 一、Ubuntu 添加账号 useradd -m -s /bin/bash -d /home/newuser newuser:newuser passwd newuser 二、 Ce…...
计算机网络、嵌入式等常见问题简答
1.嵌入式系统中经常要用到无限循环,如何用C编写死循环 答:while(1){}或者for(;;) 2.程序的局部变量存在于哪里,全局变量存在于哪里,动态申请数据存在于哪里。 答:程序的局部变量存在于栈区;全局变量存在…...
嵌入式中QT实现文本与线程控制方法
第一:利用QT进行文件读写实现 利用QT进行读写文本的时候进行读写,读取MP3歌词的文本,对这个文件进行读写操作。 实例代码,利用Qfile,对文件进行读写。 //读取对应文件文件,头文件的实现。 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #incl…...
141.环形链表 142.环形链表II
141.环形链表 & 142.环形链表II 141.环形链表 思路:快慢指针 or 哈希表 快慢指针代码: class Solution { public:bool hasCycle(ListNode *head) {if(headnullptr||head->nextnullptr)return false;ListNode *fasthead->next; //不能设置成…...
计算机网络掩码、最小地址、最大地址计算、IP地址个数
一、必备知识 1.无分类地址IPV4地址网络前缀主机号 2.每个IPV4地址由32位二进制数组成 3. /15这个地址表示网络前缀有15位,那么主机号32-1517位。 4.IP地址的个数:2**n (n表示主机号的位数) 5.可用(可分配)IP地址个数&#x…...
第3章——HTTP报文内的HTTP信息
第3章——HTTP报文内的HTTP信息 HTTP报文 用于HTTP协议交互的信息被称为HTTP报文,分为请求报文和响应报文。分为Head,Body 结构: 请求行:包含用于请求的方法,请求URI和HTTP版本。 状态行:包含表明响应…...
Minio-Linux-安装
文章目录 1.Linux安装1.下载源码包2.上传到/usr/local/minio1.进入目录2.上传 3.开放执行权限4.创建minio文件存储目录及日志目录5.编写启动的shell脚本1.脚本编写2.赋予执行权限 6.启动!1.执行run脚本2.查看日志3.开放9001和9000端口1.服务器2.安全组3.访问&#x…...
面试高阶问题:对称加密与非对称加密的原理及其应用场景
目录 第一章 对称加密原理及算法实现 第二章 非对称加密原理及算法实现 第三章 对称加密与非对称加密的应用场景 第四章 对称加密与非对称加密的应用实例 第五章 对称加密与非对称加密的对比分析 第一章 对称加密原理及算法实现 1.1 对称加密的原理 对称加密,又称私钥加密…...
报错 - decord 在 macOS Silicon 安装失败
问题:在 macOS M2 上 pip 安装 decord 出错: ERROR: Could not find a version that satisfies the requirement decord (from versions: none) ERROR: No matching distribution found for decord使用 decord 源码编译,make 也会出很多问题 …...
英伟达 RTX 5090 显卡赋能医疗大模型:变革、挑战与展望
一、英伟达 RTX 5090 与 RTX 4090 技术参数对比 1.1 核心架构与制程工艺 在探讨英伟达 RTX 4090 与 RTX 5090 的差异时,核心架构与制程工艺无疑是最为关键的基础要素,它们从根本上决定了两款显卡的性能上限与应用潜力。 1.1.1 核心架构差异 RTX 4090…...
PyCharm简单调试
本文简单讲述一下PyCharm中经常用到的调试操作。 示例代码如下: for i in range(10):print("hello", i)if i > 2:print("ok!")在代码前面打上断点,如下图所示: 单机调试按钮Debug 单机Resume Program按钮…...
快速入门Spring Cloud Alibaba,轻松玩转微服务
1 快速入门Spring Cloud Alibaba,轻松玩转微服务 1.1 架构 架构图: 1.2 项目结构 1.2.1 系统框架版本 版本适配查看:https://sca.aliyun.com/docs/2023/overview/version-explain/ Spring Boot Version :3.2.4 Spring Clo…...
浅尝Appium自动化框架
浅尝Appium自动化框架 Appium自动化框架介绍Appium原理Appium使用安装平台驱动 Appium自动化框架介绍 Appium 是一个开源的自动化测试框架,最初设计用于移动应用的测试,但现在它也扩展了对桌面端应用的支持。Appium 使得自动化测试变得更加简单…...
poi-tl+kkviewfile实现生成pdf业务报告
需求背景,需要把ai生成的一些业务数据,生成一份pdf报告 需求分析 简单来说,就是json生成pdf的方案。 直接生成pdf。适合一些pdf样式简单的场景,一般就是纯文本按序渲染,或者是纯表格。如果需要一些复杂的排布&#x…...
python导入模块失败
运行下面代码模块,出现报错,导入模块失败 import torch from layers.Embed import DataEmbedding from layers.Conv_Blocks import Inception_Block_V1 将你自己的目录添加到 sys.path,假设你的目录位置是D://winhzq//桌面//pydemo//…...
Vulkan 学习(12)---- Vulkan pipeline 创建
目录 Vulkan 渲染管线顶点输入阶段输入装配阶段顶点着色器阶段细分控制、评估着色器阶段(可选)几何着色器阶段(可选)图元装配阶段光栅化阶段片段着色器片段测试阶段混合阶段 Vulkan 渲染管线 渲染管线可以看作是一条生产流水线,定义了从输入顶点到输出图像的所有步…...
BloombergGPT: A Large Language Model for Finance——面向金融领域的大语言模型
这篇文章介绍了BloombergGPT,一个专门为金融领域设计的大语言模型(LLM)。以下是文章的主要内容总结: 背景与动机: 大语言模型(如GPT-3)在多个任务上表现出色,但尚未有针对金融领域的…...
来说数据库
什么是数据库? 是部署在操作系统上,把数据按一定的数据模型组织、永久存储,并可以被用户共享的软件系统。 其实数据库,可以理解为,把数据都存成文件,有很多的文件和很多的目录,不好管理…...
教程:从pycharm基于anaconda构建机器学习环境并运行第一个 Python 文件
1. 安装 PyCharm 访问 PyCharm 官方网站:https://www.jetbrains.com/pycharm/。下载社区版(免费)或专业版(收费,提供更多功能)。按照操作系统的安装指导安装 PyCharm。安装后打开 PyCharm,并根…...
嵌入式驱动开发详解11(INPUT子系统)
文章目录 前言input子系统简介主要结构体API函数input子系统驱动框架上报事件后续设备树配置方式参考文献 前言 按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做 input 子系统的框架来处理输入事件。输入设备本质上还是字符设备&…...
动态规划解决目标和问题
代码随想录链接:代码随想录 思路: 可以将数组分为两部分,其中一部分记作left,其中数字的符号全为,而另外一部分记作right,其中数字的符号全为-。这里全为-的意思不是真正的符号为-,而表示这一堆数字在计算时取值为负 因此有如下…...
【漏洞分析】UDF提权漏洞——CVE-2016-6662-MySQL ‘malloc_lib’变量重写命令执行
0x00 前言 最近在做渗透笔记,其中有一个靶机在getshell后,需要进行提权。发现靶机使用root启动的mysql服务,那么尝试使用UDF提权。于是在提权成功后,花了一天时间特意搜了一下整个UDF提权的漏洞原理和利用,加深理解。…...
特种设备安全管理人员免费题库限时练习(判断题)
56.(判断题)特别重大事故、重大事故、较大事故和一般事故,负责事故调查的人民政府应当自收到事故调查报告之日起15日内做出批复。 A.正确 B.错误 答案:错误 57.(判断题)每一类事故灾难的应急救援措施可能千差万别,因此其基本应急模式是不一致的。 A.正确 B.错误 答案:错…...
linux-25 文件管理(三)复制、移动文件,cp,mv
命令cp是copy的简写,而mv则是move的简写。那既然copy是用于实现复制文件的,那通常一般我们要指定其要复制的是谁?而且复制完以后保存在什么地方,对吧?那因此它的使用格式很简单,那就是cp srcfile dest&…...
中国科技统计年鉴EXCEL版(2021-2023年)-社科数据
中国科技统计年鉴EXCEL版(2021-2023年)-社科数据https://download.csdn.net/download/paofuluolijiang/90028724 https://download.csdn.net/download/paofuluolijiang/90028724 中国科技统计年鉴提供了从2021至2023年的详尽数据,覆盖了科技…...
Idea(中文版) 项目结构/基本设置/设计背景
目录 1. Idea 项目结构 1.1 新建项目 1.2 新建项目的模块 1.3 新建项目模块的包 1.4 新建项目模块包的类 2. 基本设置 2.1 设置主题 2.2 设置字体 2.3 设置注释 2.4 自动导包 2.5 忽略大小写 2.6 设置背景图片 3. 项目与模块操作 3.1 修改类名 3.2 关闭项目 1. I…...
jenkins入门--安装jenkins
下载地址https://www.jenkins.io/ jdk 安装 :Jenkins需要安装对应版本的jdk,我在安装过程中显示需要21,17 Java Downloads | Oracle jenkins安装过程参考全网最清晰Jenkins安装教程-windows_windows安装jenkins-CSDN博客 安装完成后,浏览器输入127.0.…...
基于Springboot + vue实现的小型养老院管理系统
🥂(❁◡❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞 💖📕🎉🔥 支持我:点赞👍收藏⭐️留言📝欢迎留言讨论 🔥🔥&…...
shell基础使用及vim的常用快捷键
一、shell简介 参考博文1 参考博文2——shell语法及应用 参考博文3——vi的使用 在linux中有很多类型的shell,不同的shell具备不同的功能,shell还决定了脚本中函数的语法,Linux中默认的shell是 / b in/ b a s h ,流行的shell…...