【经验分享】CANOPEN协议驱动移植(基于CANfestival源码架构)

news2024/9/19 10:39:20

【经验分享】CANOPEN协议驱动移植(基于CANfestival源码架构)

  • 前言
  • 一、CANOPEN整体实现原理
  • 二、CANOPEN驱动收发
  • 三、Timer定时器
  • 四、Object Dictionary对象字典
  • 五、CANOPEN应用层接口
  • 六、CANOPEN 驱动移植经验
  • 总结


前言

本次CANOPEN移植基于CANfestival开源代码,整体参考了如下文章:
基于STM32F4的CANOpen移植教程(超级详细)
谈谈自己对CANOPEN协议的驱动移植理解。每个移植CANOPEN协议的请务必认真阅读《周立功CANopen 轻松入门》,其中的内容生动形象,对你移植CANOPEN代码会有很大帮助。

CANopen的难点在于需要掌握的知识点比较多,如果没有移植过类似于Ethercat等协议,对新手来说并不算容易。如果移植过协议类驱动,那入手相对容易一些。


一、CANOPEN整体实现原理

带OS(操作系统)的整体实现原理
在这里插入图片描述
不带OS的整体实现原理
在这里插入图片描述
不管是带OS还是不带OS,都需要注意三个要点
1、CAN驱动收发实现
2、Timer定时器实现
3、Object Dictionary对象字典实现
这三点贯穿CANOPEN驱动调试整个过程,实现成功基本就不会有太大问题了,后续详细讲解。
CANOPEN协议的报文格式和CAN的消息格式区别不大,唯一的区别在于COB-ID的区别,COB-ID由Funciton code(功能码)和NODE ID构成。
在这里插入图片描述
这部分了解即可,例如0x580+NODE ID为SDO接收,0x600+NODE ID为SDO发送。
在这里插入图片描述
由此引出三种模型
在这里插入图片描述
第一种是主从站模型,一主多从模型,网络管理基于此模型。
第二种是客户端/服务器模型,一般是主站作为客户端,从站作为服务器端,SDO传输基于此模型。
第三种是消费者/生产者模型,这在《周立功CANopen 轻松入门》中有很生动的解释,生产者数据发送之后,消费者只接收不回复,就像买菜一样,PDO运行基于此模型。


二、CANOPEN驱动收发

CANOPEN调用的接口
1、canSend(CAN_PORT notused, Message *m)
canSend是canfestival协议实现的关键底层接口函数,最终调用的是CAN的底层驱动发送接口。

查看Message结构体定义,如果原来就有CAN驱动发送接口,对接上即可,转换下并不难。

typedef struct {
  UNS16 cob_id;	/**< message's ID */
  UNS8 rtr;		/**< remote transmission request. (0 if not rtr message, 1 if rtr message) */
  UNS8 len;		/**< message's length (0 to 8) */
  UNS8 data[8]; /**< message's datas */
} Message;

如果原先没有CAN底层发送接口,那么建议先实现CAN收发,再来实现CANOPEN收发。AT91的实现如下

unsigned char canSend(CAN_PORT notused, Message *m)
/******************************************************************************
The driver send a CAN message passed from the CANopen stack
INPUT	CAN_PORT is not used (only 1 avaiable)
	Message *m pointer to message to send
OUTPUT	1 if  hardware -> CAN frame
******************************************************************************/
{
  unsigned int mask;
  AT91S_CAN_MB *mb_ptr = AT91C_BASE_CAN_MB0 + START_TX_MB;

  if ((AT91F_CAN_GetStatus(AT91C_BASE_CAN) & TX_INT_MSK) == 0)
    return 0;			// No free MB for sending

  for (mask = 1 << START_TX_MB;
       (mask & TX_INT_MSK) && !(AT91F_CAN_GetStatus(AT91C_BASE_CAN) & mask);
	mask <<= 1, mb_ptr++)	// Search the first free MB
  {
  }
  AT91F_CAN_CfgMessageIDReg(mb_ptr, m->cob_id, 0);	// Set cob id
  // Mailbox Control Register, set remote transmission request and data lenght code
  AT91F_CAN_CfgMessageCtrlReg(mb_ptr, m->rtr ? AT91C_CAN_MRTR : 0 | (m->len << 16));	
  AT91F_CAN_CfgMessageDataLow(mb_ptr, *(UNS32*)(&m->data[0]));// Mailbox Data Low Reg
  AT91F_CAN_CfgMessageDataHigh(mb_ptr, *(UNS32*)(&m->data[4]));// Mailbox Data High Reg
  // Start sending by writing the MB configuration register to transmit
  AT91F_CAN_InitTransferRequest(AT91C_BASE_CAN, mask);
  return 1;	// successful
}

2、canReceive(Message *m)
CANOPEN发送接口一般在CAN中断中实现,主要用来实现当收到CANOPEN消息后,进行CANOPEN的协议解析,协议解析的接口为canDispatch函数。

unsigned char canReceive(Message *m)
/******************************************************************************
The driver passes a received CAN message to the stack
INPUT	Message *m pointer to received CAN message
OUTPUT	1 if a message received
******************************************************************************/
{
  unsigned int mask;
  AT91S_CAN_MB *mb_ptr = AT91C_BASE_CAN_MB0;

  if ((AT91F_CAN_GetStatus(AT91C_BASE_CAN) & RX_INT_MSK) == 0)
    return 0;		// Nothing received

  for (mask = 1;
       (mask & RX_INT_MSK) && !(AT91F_CAN_GetStatus(AT91C_BASE_CAN) & mask);
	mask <<= 1, mb_ptr++)	// Search the first MB received
  {
  }
  m->cob_id = AT91F_CAN_GetFamilyID(mb_ptr);
  m->len = (AT91F_CAN_GetMessageStatus(mb_ptr) & AT91C_CAN_MDLC) >> 16;
  m->rtr = (AT91F_CAN_GetMessageStatus(mb_ptr) & AT91C_CAN_MRTR) ? 1 : 0;
  *(UNS32*)(&m->data[0]) = AT91F_CAN_GetMessageDataLow(mb_ptr);
  *(UNS32*)(&m->data[4]) = AT91F_CAN_GetMessageDataHigh(mb_ptr);
  // Enable Reception on Mailbox
  AT91F_CAN_CfgMessageModeReg(mb_ptr, AT91C_CAN_MOT_RX | AT91C_CAN_PRIOR);
  AT91F_CAN_InitTransferRequest(AT91C_BASE_CAN, mask);
  return 1;		// message received
}

3、can_irq_handler
can中断函数实现,当中断来临时判断,如果接收到消息就进行canopen协议解析。

void can_irq_handler(void)
/******************************************************************************
CAN Interrupt
******************************************************************************/
{
  volatile unsigned int status;
  static Message m = Message_Initializer;		// contain a CAN message
		
  status = AT91F_CAN_GetStatus(AT91C_BASE_CAN) & AT91F_CAN_GetInterruptMaskStatus(AT91C_BASE_CAN);

  if(status & RX_INT_MSK)
  {	// Rx Interrupt
    if (canReceive(&m))			// a message received
      canDispatch(&ObjDict_Data, &m);         // process it
  }
}

canDispatch原型如下:

void canDispatch(CO_Data* d, Message *m)
{
	UNS16 cob_id = UNS16_LE(m->cob_id);
	 switch(cob_id >> 7)
	{
		case SYNC:		/* can be a SYNC or a EMCY message */
			if(cob_id == 0x080)	/* SYNC */
			{
				if(d->CurrentCommunicationState.csSYNC)
					proceedSYNC(d);
			} else 		/* EMCY */
				if(d->CurrentCommunicationState.csEmergency)
					proceedEMCY(d,m);
			break;
		case TIME_STAMP:
		case PDO1tx:
		case PDO1rx:
		case PDO2tx:
		case PDO2rx:
		case PDO3tx:
		case PDO3rx:
		case PDO4tx:
		case PDO4rx:
			if (d->CurrentCommunicationState.csPDO)
				proceedPDO(d,m);
			break;
		case SDOtx:
		case SDOrx:
			if (d->CurrentCommunicationState.csSDO)
				proceedSDO(d,m);
			break;
		case NODE_GUARD:
			if (d->CurrentCommunicationState.csLifeGuard)
				proceedNODE_GUARD(d,m);
			break;
		case NMT:
			if (*(d->iam_a_slave))
			{
				proceedNMTstateChange(d,m);
			}
            break;
#ifdef CO_ENABLE_LSS
		case LSS:
			if (!d->CurrentCommunicationState.csLSS)break;
			if ((*(d->iam_a_slave)) && cob_id==MLSS_ADRESS)
			{
				proceedLSS_Slave(d,m);
			}
			else if(!(*(d->iam_a_slave)) && cob_id==SLSS_ADRESS)
			{
				proceedLSS_Master(d,m);
			}
			break;
#endif
	}
}

该函数对接收到的信息首先进行COB-ID判断是什么类型,然后进行相应的报文处理。

各函数实现可以参考CANfestival中examples中的实现,带OS和不带OS的都有。
在这里插入图片描述


三、Timer定时器

这是第二个重点,各驱动的实现各有不同,需要实现:
1、void initTimer(void) 初始化定时器
2、void setTimer(TIMEVAL value) 用于设置下一个定时器报警时间
3、TIMEVAL getElapsedTime(void) 该函数用于获取自上次调用以来所经过的时间。它通过复制运行中的计时器的值,然后计算当前计时器值与上次调用时计时器值之间的差异来实现。
4、void timer_can_irq_handler(void) 此函数处理定时器中断,定时时间到即处理,调用TimeDispatch函数更新栈中的时间信息。TimeDispatch函数原型如下:

void TimeDispatch(void)
{
	TIMER_HANDLE i;
	TIMEVAL next_wakeup = TIMEVAL_MAX; /* used to compute when should normaly occur next wakeup */
	/* First run : change timer state depending on time */
	/* Get time since timer signal */
	UNS32 overrun = (UNS32)getElapsedTime();

	TIMEVAL real_total_sleep_time = total_sleep_time + overrun;

	s_timer_entry *row;

	for(i=0, row = timers; i <= last_timer_raw; i++, row++)
	{
		if (row->state & TIMER_ARMED) /* if row is active */
		{
			if (row->val <= real_total_sleep_time) /* to be trigged */
			{
				if (!row->interval) /* if simply outdated */
				{
					row->state = TIMER_TRIG; /* ask for trig */
				}
				else /* or period have expired */
				{
					/* set val as interval, with 32 bit overrun correction, */
					/* modulo for 64 bit not available on all platforms     */
					row->val = row->interval - (overrun % (UNS32)row->interval);
					row->state = TIMER_TRIG_PERIOD; /* ask for trig, periodic */
					/* Check if this new timer value is the soonest */
					if(row->val < next_wakeup)
						next_wakeup = row->val;
				}
			}
			else
			{
				/* Each armed timer value in decremented. */
				row->val -= real_total_sleep_time;

				/* Check if this new timer value is the soonest */
				if(row->val < next_wakeup)
					next_wakeup = row->val;
			}
		}
	}

	/* Remember how much time we should sleep. */
	total_sleep_time = next_wakeup;

	/* Set timer to soonest occurence */
	setTimer(next_wakeup);

	/* Then trig them or not. */
	for(i=0, row = timers; i<=last_timer_raw; i++, row++)
	{
		if (row->state & TIMER_TRIG)
		{
			row->state &= ~TIMER_TRIG; /* reset trig state (will be free if not periodic) */
			if(row->callback)
				(*row->callback)(row->d, row->id); /* trig ! */
		}
	}
}

该函数实现定时器调度功能:

计算自上次信号以来的时间偏移。
遍历所有定时器,根据是否已触发或周期到期更新状态和下次触发时间。
根据最近需触发的定时器设置系统睡眠时间和实际定时器值。
再次遍历并调用已触发定时器的回调函数。可以参考
CANopen补充–时间计算出错


四、Object Dictionary对象字典

对象字典是连接底层和应用层通信的重要桥梁,没有对象字典就无法解析SDO和PDO等报文。对象字典的生成依赖于如下python工具objdictedit,在canfestival源码里。
在这里插入图片描述
根据sdo和pdo的要求进行配置,注意主站需要配置成client,从站配置成server。pdo配置,主站的tpdo和从站的rpdo cob-id要一致,主站的rpdo和从站的tpdo要一致,否则无法获取到数据。
在这里插入图片描述
配置完成后点击建立词典即可生成Master.c和Master.h文件。
在这里插入图片描述
需要注意,生成后的文件可能还需要进行二次修改,不一定可以直接使用。Master.c最后一句是连接主函数和对象字典的关键。所以一定要匹配上。
main.c函数,引用对象字典Master_Data
在这里插入图片描述
Master.c函数Master_Data定义
在这里插入图片描述
Co_Data这个结构体包含了对象字典解析后的关键信息,在调试过程中也可以查看,比如是否存在越界,空指针等问题。详细的不再赘述,可以参考网上相关文章。

struct struct_CO_Data {
	/* Object dictionary */
	UNS8 *bDeviceNodeId;
	const indextable *objdict;
	s_PDO_status *PDO_status;
	TIMER_HANDLE *RxPDO_EventTimers;
	void (*RxPDO_EventTimers_Handler)(CO_Data*, UNS32);
	const quick_index *firstIndex;
	const quick_index *lastIndex;
	const UNS16 *ObjdictSize;
	const UNS8 *iam_a_slave;
	valueRangeTest_t valueRangeTest;
	
	/* SDO */
	s_transfer transfers[SDO_MAX_SIMULTANEOUS_TRANSFERS];
	/* s_sdo_parameter *sdo_parameters; */

	/* State machine */
	e_nodeState nodeState;
	s_state_communication CurrentCommunicationState;
	initialisation_t initialisation;
	preOperational_t preOperational;
	operational_t operational;
	stopped_t stopped;
     void (*NMT_Slave_Node_Reset_Callback)(CO_Data*);
     void (*NMT_Slave_Communications_Reset_Callback)(CO_Data*);
     
	/* NMT-heartbeat */
	UNS8 *ConsumerHeartbeatCount;
	UNS32 *ConsumerHeartbeatEntries;
	TIMER_HANDLE *ConsumerHeartBeatTimers;
	UNS16 *ProducerHeartBeatTime;
	TIMER_HANDLE ProducerHeartBeatTimer;
	heartbeatError_t heartbeatError;
	e_nodeState NMTable[NMT_MAX_NODE_ID]; 

	/* NMT-nodeguarding */
	TIMER_HANDLE GuardTimeTimer;
	TIMER_HANDLE LifeTimeTimer;
	nodeguardError_t nodeguardError;
	UNS16 *GuardTime;
	UNS8 *LifeTimeFactor;
	UNS8 nodeGuardStatus[NMT_MAX_NODE_ID];

	/* SYNC */
	TIMER_HANDLE syncTimer;
	UNS32 *COB_ID_Sync;
	UNS32 *Sync_Cycle_Period;
	/*UNS32 *Sync_window_length;;*/
	post_sync_t post_sync;
	post_TPDO_t post_TPDO;
	post_SlaveBootup_t post_SlaveBootup;
    post_SlaveStateChange_t post_SlaveStateChange;
	
	/* General */
	UNS8 toggle;
	CAN_PORT canHandle;	
	scanIndexOD_t scanIndexOD;
	storeODSubIndex_t storeODSubIndex; 
	
	/* DCF concise */
    const indextable* dcf_odentry;
	UNS8* dcf_cursor;
	UNS32 dcf_entries_count;
	UNS8 dcf_status;
    UNS32 dcf_size;
    UNS8* dcf_data;
	
	/* EMCY */
	e_errorState error_state;
	UNS8 error_history_size;
	UNS8* error_number;
	UNS32* error_first_element;
	UNS8* error_register;
    UNS32* error_cobid;
	s_errors error_data[EMCY_MAX_ERRORS];
	post_emcy_t post_emcy;
	
#ifdef CO_ENABLE_LSS
	/* LSS */
	lss_transfer_t lss_transfer;
	lss_StoreConfiguration_t lss_StoreConfiguration;
#endif	
};

五、CANOPEN应用层接口

上述移植完成后驱动大部分内容已经完成,接下来就是应用层调用什么接口进行主从站的通信。在例子中可以看到,主站进入工作状态y以及设置NodeID,需要调用如下接口
1、setState设置状态

  setState(&ObjDict_Data, Initialisation);	// Init the state
  setNodeId (&ObjDict_Data, 0x7F);
  setState(&ObjDict_Data, Operational);		// Put the master in operational mode
	

2、masterSendNMTstateChange设置从站状态

UNS8 masterSendNMTstateChange(CO_Data* d, UNS8 nodeId, UNS8 cs)
{
  Message m;

  MSG_WAR(0x3501, "Send_NMT cs : ", cs);
  MSG_WAR(0x3502, "    to node : ", nodeId);
  /* message configuration */
  m.cob_id = 0x0000; /*(NMT) << 7*/
  m.rtr = NOT_A_REQUEST;
  m.len = 2;
  m.data[0] = cs;
  m.data[1] = nodeId;

  return canSend(d->canHandle,&m);
}

可以发现最终都是调用canSend进行底层报文发送出去。
3、masterSendNMTnodeguard用来设置从站节点守护进程,设置成功后可以收到从站的心跳报文。
4、sendsdo用来发送服务数据对象sdo命令,直接调用即可。
5、过程数据对象发送pdo比较特殊,有好几种通信方式。选择FEh或者FFh后,再设置Event timer,tpdo就会自动发送。(注意:TPDO和RPDO是相对于自身来定义的,T发送,R接收)。
在这里插入图片描述
6、写字典和读字典setODentry和getODentry,可以用来改变对象字典中的参数,Pdo过程中的数据传递等,注意各输入参数的定义。


六、CANOPEN 驱动移植经验

1、timer定时器调试过程中需要注意时间溢出问题,避免出现定时不准,如果不重新开定时器也可以用系统时钟。把timer加入监控内容,调试过程中需要注意是否停止。
2、canfestival默认是开启串口Log的,可以借助串口工具进行开发。
在这里插入图片描述

研发阶段结束后需要关闭,避免打印的延时。

#define DEBUG_WAR_CONSOLE_ON
#define DEBUG_ERR_CONSOLE_ON

3、借助支持canopen协议的工具可以直接看到传输的协议内容,对于调试有很大帮助,建议备一个,如果实在没有就接串口。
在这里插入图片描述
4、有些canfestival源码可能存在bug,根据实际情况依然需要查看源码进行修改,不要觉得源码必然靠谱。
5、canSend转换到can底层传输一定要注意RTR,数据帧用的比较多,但是一定不能省。
6、想到再补充。


总结

CANOPEN协议移植主要调试时间花在timer定时器、can发送和中断实现和对象字典的实现上,其他接口都是统一通用的,只要知道调用哪个接口就可以实现。时间仓促讲的不是很详细,有什么问题可以留言。

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

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

相关文章

开关电源中“黑箱”的考虑

在初设计阶段&#xff0c;首先要考虑开关电源的一些主要参数&#xff0c;这有助于设计者确 定自己所选的拓扑是否正确&#xff0c;也便于提前预定实验板所需的元器件。同时可以知 道接下来的设计所需的一些非常重 要的参数。关于如何对“黑箱”进 行估计&#xff0c;设计者只要…

MIPI联盟D-PHYv1.2规范阅读笔记

本文阅读自eetop.cn_mipi_D-PHY_specification_v1-2.pdf MIPI简介 MIPI 联盟成立至今制定了多种用于不同终端设备的接口标准&#xff0c;其中包括 用于摄像头的 CSI-2。 用于显示器的 DSI。 用于射频的 DigRF 。 用于麦克风的SLIMbus等接口协议。 MIPI CSI-2 协议简介 两…

书生大模型实战营(第三期闯关大挑战)- 进阶岛 第五关 茴香豆:企业级知识库问答工具

茴香豆本地标准版搭建 茴香豆介绍 茴香豆 是由书生浦语团队开发的一款开源、专门针对国内企业级使用场景设计并优化的知识问答工具。在基础 RAG 课程中我们了解到&#xff0c;RAG 可以有效的帮助提高 LLM 知识检索的相关性、实时性&#xff0c;同时避免 LLM 训练带来的巨大成…

8周流水6700美元Dropshipping运营全流程曝光丨出海笔记

&#xff08;之前删掉补发系列&#xff09; 之前分享了一个案例《净赚4000多美元&#xff01;个人卖家Dropshipping卖30天太阳镜&#xff0c;究竟如何做到的》&#xff0c;不少小伙伴觉得实操性很强&#xff0c;纷纷反馈意犹未尽&#xff0c;所以船长继续去找之前的Dropshippin…

房产系统技术功能解析

房产系统的功能设计旨在提高房地产行业的运作效率、优化资源分配&#xff0c;并为用户提供更便捷高效的服务体验。以下是房产系统关键技术功能的详细解析&#xff1a; 一、房源管理 房源信息录入与编辑&#xff1a;支持全面的房源信息录入&#xff0c;包括房屋位置、面积、户型…

C++第四十一弹---C++11新特性深度解析:让你的代码更现代、更高效(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1. C11简介 2. 统一的列表初始化 2.1 &#xff5b;&#xff5d;初始化 2.2 std::initializer_list 3. 声明 3.1 auto 3.2 decltype 3.3 nu…

arcgis依据字段分组

脚本代码&#xff1a; UniqueDict {} def isDuplicateIndex(inValue): UniqueDict.setdefault(inValue,0) UniqueDict[inValue] 1 return UniqueDict[inValue] 输出值 isDuplicateIndex( !地块编号! )

临床试验中缺失数据的问题讨论

一、数据缺失的原因&#xff1a; &#xff08;1&#xff09;AE或疗效退出&#xff1b; &#xff08;2&#xff09;结局变量不适用&#xff08;无法获得结局变量&#xff09;&#xff1b; &#xff08;3&#xff09;失访&#xff1b; &#xff08;4&#xff09;数据采集失误&am…

云安全已经很好,但如何让它更好呢

尽管云计算很安全&#xff0c;但并不能完全避免数据泄露。随着云计算逐渐成为IT的重要部分&#xff0c;现在企业必须更认真地考虑如何加强云服务提供商默认安全基础设施的安全性。 传统云服务提供商都在努力为其客户提供强大的安全措施&#xff0c;他们通常会提供服务器端加密…

佰朔资本:市场转机正在逐步孕育 关注银行、电力等板块

商场起色正在逐渐孕育。中报宣布期即将以前&#xff0c;商场将完结盈利预期下修&#xff0c;一同美联储9月降息信号激烈&#xff0c;若协作国内方针加码发力信号&#xff0c;商场有望翻开向上空间。短期除高胜率的稳定盈利资产和中报超预期且景气继续方向&#xff0c;亦可注重中…

ES(索引数据库)导入MySQL全量(批量导入)和增量数据Canal增量数据同步利器

索引库数据管理 秒杀商品数量庞大&#xff0c;我们要想实现快速检索&#xff0c;不建议直接使用关系型数据库查找。不建议使用Redis缓存所有数据&#xff0c;因为秒杀商品量大&#xff0c;会影响Redis的性能&#xff0c;并且Redis的条件检索能力偏弱。我们可以使用Elasticsear…

Linux:NAT等相关问题

目录 1&#xff1a;NAT背景 2&#xff1a;NAT IP转换过程 3&#xff1a;NATP 4&#xff1a;正向代理 5&#xff1a;反向代理 6&#xff1a;NAT和代理服务器 应用场景 实现方法 1&#xff1a;NAT背景 IPv4地址耗尽&#xff1a;随着互联网的迅速发展&#xff0c;连接到…

[Leetcode 105][Medium] 从前序与中序遍历序列构造二叉树-递归

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 题目地址 二、整体思路 前序遍历得到的是[根结点|左子树|右子树]&#xff0c;中序遍历得到的是[左子树|根结点|右子树] 那么可以设立一个递归函数&#xff0c;作用是利用前序遍历的数组和中序遍历的数组构建一个节点…

汇川技术|Inoproshop软件菜单[在线、调试]

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 现如今学习资源是容易获取了&#xff0c;像我网盘里堆了7T的资料&#xff0c;有很多还没看过&#xff0c;总是见到了就收藏起来&#xff0c;但是真的看不过来啊。有时间和精力的小伙伴可以找自己感兴趣的看起来。 本…

景商场双目客流量摄像机,具有100°宽视角,识别范围广

在当今竞争激烈的商业环境中&#xff0c;商场管理者们一直在寻求更有效的方法来了解顾客行为、优化运营策略。商场双目客流量摄像机的出现&#xff0c;为商场管理带来了新的机遇。 一、功能强大 商场双目客流量摄像机具有多项强大功能。首先&#xff0c;它拥有 100 宽视角&…

雷达水文监测站

雷达水文监测站是一种利用雷达技术进行水文监测的设备&#xff0c;其功能主要包括以下几个方面&#xff1a; 水位监测&#xff1a;雷达水文监测站可以实时监测水体的水位变化&#xff0c;通过测量水面到雷达发射器的距离来计算水位。 流量监测&#xff1a;根据水位的变化&…

西门子一个PLC两个HMI分别显示不同报警内容

当前项目为一个PLC带两个HMI&#xff0c;功能上两个站完全分离&#xff0c;但是为了避免重复绘制HMI&#xff0c;先将两个站点报警链接到同一个HMI上&#xff0c;同时又需要指定站点的HMI单独显示该站点的报警&#xff1b;否则会出现如下情况&#xff0c;两个站都显示全部的报警…

传输大咖33 | 适合企业内外网文件交换系统是怎样的?

企业的内外网文件交换是企业日常运营的重要环节。然而&#xff0c;随着技术的发展&#xff0c;企业的文件数据量日益增长&#xff0c;文件的格式也越来越复杂多样。传统的内外网文件交换方式也逐渐显露出不足之处&#xff0c;对于企业来说&#xff0c;寻求更加高效、安全、可靠…

YOLOv9改进策略【损失函数篇】| 利用MPDIoU,加强边界框回归的准确性

一、背景 目标检测和实例分割中的关键问题&#xff1a; 现有的大多数边界框回归损失函数在不同的预测结果下可能具有相同的值&#xff0c;这降低了边界框回归的收敛速度和准确性。 现有损失函数的不足&#xff1a; 现有的基于 ℓ n \ell_n ℓn​范数的损失函数简单但对各种尺度…

Word文件密码忘记,该如何才能编辑Word文件呢?

Word文件打开之后&#xff0c;发现编辑功能都是灰色的&#xff0c;无法使用&#xff0c;无法编辑&#xff0c;遇到这种情况&#xff0c;是因为Word文件设置了限制编辑导致的。一般情况下&#xff0c;我们只需要输入Word密码&#xff0c;将限制编辑取消就可以正常编辑文件了&…