干货 | 动手写写Modbus-RTU协议

嵌入式从0到1 2021-09-06 08:45
关注、星标嵌入式客栈,精彩及时送达

[导读] 大家好,我是逸珺。

前面聊了modbus的物理层,协议标准,今天来实现一下modbus-RTU,本文主要聊从设备的实现。

思路分析

前面聊modbus协议的时候,画了这张图modbus OSI分层模型图。OSI模型图是一种分层设计图。就好比建房子,那些搞建筑的绘制的设计图一样,所以为什么建筑师也叫Architect。嗨,跑偏了。

所以编码之前,这个协议照这个图的意思就是最好设计成三层,比如这样:

物理层与控制芯片采用UART与一个GPIO进行交互:

  • UART,一般的单片机/DSP/MPU都具有UART外设,其中TXD将二进制报文,逐字节按照UART规范,进行编码,一般一个字节需要11个bit在物理层编码,事实上很多时候也会使用10bit模式,无校验位,8个数据位,1个起始位,1个停止位。

字节编码
  • DIR:利用一个芯片的GPIO,实现收发方向控制,DIR置低,为接收模式;DIR置高,为发送模式。

链路层

链路层的职责是:

  • 实现报文的接收服务
  • 实现介质的发送服务
  • 介质管理

报文接收服务,芯片与物理层之间通信接口是UART,因此就是处理串口接收。先看看modbus报文的定义:

报文结构

modbus报文,没有特殊帧头、帧尾,如何判别接收到一个完整的帧了呢?

modus 标准规定,帧间隔至少须3.5个字节时间,字节间隔不得大于1.5 字节时间,那反过来思考,只要3.5 字节时间内没有新收到数据就表示有可能接收到一帧。为什么是有可能呢?因为数据里还有可能有错误字节,如果加上CRC 校验通过这个条件,就可以判定数据帧收到了。 所以在上文中才会有这么一个状态机图:

收发状态机

这里的T3.5,T1.5字节时间怎么算呢?前面说了一个字节需要11个Bit表示,所以如果波特率是9600,就按照下面计算,如果是其他的波特率计算方式一样。

用个定时器就可以实现了。

#define MODBUS_BUF_SIZE  256U

typedef enum {
 E_MS_RECEVING,
 E_MS_PENDING, 
 E_MS_SENDING 
}E_MODBUS_STATE;

typedef struct _T_MODBUS_LAYER2
{

 INT8U  buffer[MODBUS_BUF_SIZE];
 INT16U index;
 INT16U txLength;
 E_MODBUS_STATE state; 

 void (*SendDataToCom)(INT16U length);
 void (*InitiliseLayer2)(void);
} T_MODBUS_LAYER2;
extern volatile T_MODBUS_LAYER2 modbusLayer2;

为突出重点,接收控制中仅实现T3.5字节时间判定,T1.5字节时间要实现也非常容易,每接收一个字节就判定一下间隔时间即可,超过1.5字节时间,丢弃所有字节,重新开始接收就可以了。

//假定用P05脚控制收发方向
sbit MODBUS_COM1_CTL = P0^5;

#define MODBUS_COM1_R_ENABLE   MODBUS_COM1_CTL = 0
#define MODBUS_COM1_R_DISABLE  MODBUS_COM1_CTL = 1

#define FOSC       11059200
#define TIMER_CLK   921600
#define BAUD_9600     9600
//这里实现T3.5 如果波特率可修改,这里需要调整
#define COM1_T35_GAP_TIME ( (INT16U)(65536-(TIMER_CLK*10*3.5/BAUD_9600) ) )

void ModbusCom1StartGapTimer(void)
{
 ET0 = 0;
 TR0 = 0;
 TMOD  = 0X21;
 CKCON = 0x00;
 TH0 = (INT8U)((COM1_T35_GAP_TIME&0xff00)>>8);
 TL0 = (INT8U)(COM1_T35_GAP_TIME&0x00ff);
 ET0 = 1;
 TR0 = 1
}

void ModbusCom1StopGapTimer(void)
{
 ET0 = 0;
 TR0 = 0;
}

void ModbusCom1InitLayer2_SRV(void)
{
 MODBUS_COM1_R_DISABLE;
 ModbusCom1InitUart((INT16U)BAUD_9600);

 modbusLayer2.state = E_MODBUS_STATE_RECEVING;
 modbusLayer2.index = 0;
 modbusLayer2.txLength = 0;
 modbusLayer2.SendDataToCom   = ModbusCom1SendData;
 modbusLayer2.InitiliseLayer2 = ModbusCom1InitLayer2;
 
 MODBUS_COM1_R_ENABLE;
}

void ModbusCom1InitLayer2(void)
{
 ES1   = 0;
 MODBUS_COM1_R_DISABLE;
 modbusLayer2[MODBUS_COM1].state = E_MODBUS_STATE_RECEVING;
 modbusLayer2[MODBUS_COM1].index = 0;
 modbusLayer2[MODBUS_COM1].txLength = 0;
  
 MODBUS_COM1_R_ENABLE;
 TI_1  = 0
 RI_1  = 0;
 ES1   = 1// 使能串口
}

void TIMER0_INT_ISR(void) interrupt 1 using 1
{   
 MODBUS_COM1_R_DISABLE;
 ES1 = 0
 ModbusCom1StopGapTimer();   
 modbusLayer2.state = E_MODBUS_STATE_PENDING; 
 TF0 = 0;
}

void ModbusCom1InitUart(INT16U baudrate)
{
 baudrate = 9600;
 T2CON &= 0XCF// XXXX XXXX  Timer 2 Control 
          // |||| |||+- CP/RL2 Capture/Reload Select.
          // |||| |||  0 = Auto-reloads will occur when Timer 2 overflows or
          // |||| |||     a falling edge is detected on T2EX if EXEN2 = 1.
          // |||| |||  1 = Timer 2 captures when a falling edge is detected on T2EX if EXEN2 = 1.
          // |||| ||+-- Counter/Timer Select.
          // |||| ||   0 = Timer 2 functions as a timer.
          // |||| ||   1 = Timer 2 will count negative transitions on the T2 pin (P1.0).
          // |||| |+--- Timer 2 Run Control.
          // |||| |   0 = Timer 2 is halted.
          // |||| |   1 = Timer 2 is enabled.
          // |||| +---- Timer 2 External Enable.
          // ||||     0 = Timer 2 will ignore all external events at T2EX.
          // ||||     1 = Timer 2 will capture or reload a value if a negative transition is detected on the T2EX pin.
          // |||+------ Transmit Clock Flag.
          // |||     0 = Timer 1 overflow is used to Tx baud rate for USART0.
          // |||     1 = Timer 2 overflow is used to Tx baud rate for USART0.
          // ||+------- Receive Clock Flag
          // ||     0 = Timer 1 overflow is used to Rx baud rate for USART0.
          // ||     1 = Timer 2 overflow is used to Rx baud rate for USART0.
          // |+-------- Timer 2 External Flag.
          // |      A negative transition on the T2EX pin (P1.1) will cause this flag 
          // +--------- Timer 2 Overflow Flag.
 
 SMOD1 = 0;     
 //baudrate 
 //T0 T1 T2 12分频 00000000 
 CKCON = 0x00;
 // XXXX XXXX  Clock Control 
 // |||| |+++- Stretch MOVX Select 2:0.
 // |||| |   000~111= 2~9 Instruction Cycles
 // |||| +---- Timer 0 Clock Select.
 // ||||     0: Timer 0 uses a divide by 12 of the crystal frequency.
 // ||||     1: Timer 0 uses a divide by 4 of the crystal frequency.
 // |||+------ Timer 1 Clock Select. 
 // |||     0: Timer 1 uses a divide by 12 of the crystal frequency
 // |||     1: Timer 1 uses a divide by 4 of the crystal frequency.
 // ||+------- Timer 2 Clock Select
 // ||     0: Timer 2 uses a divide by 12 of the crystal frequency.
 // ||     1: Timer 2 uses a divide by 4 of the crystal frequency. 
 // ++-------- 00 reserved
 
 TCON  = 0X40;
 // XXXX XXXX  Timer/Counter Control
 // |||| |||+- Interrupt 0 Type Select.
 // |||| |||  0: INT0 is level-triggered.
 // |||| |||  1: INT0 is edge-triggered.
 // |||| ||+-- Interrupt 0 Edge Detect
 // |||| ||  If IT0 = 1, this bit will remain set until cleared in software 
 // |||| ||        or the start of the External Interrupt 0 service routine
 // |||| ||  If IT0 = 0, this bit will inversely reflect the state of the INT0 pin.
 // |||| |+--- Interrupt 1 Type Select.
 // |||| |    0: INT1 is level-triggered.
 // |||| |    1: INT1 is edge-triggered.
 // |||| +---- Interrupt 1 Edge Detect. similar with Interrupt 0 Edge Detect
 // |||+------ Timer 0 Run Control.
 // |||    0: Timer is halted
 // |||    1: Timer is enabled.
 // ||+------- Timer 0 Overflow Flag.
 // ||     0: No Timer 0 overflow has been detected.
 // ||     1: Timer 0 has overflowed its maximum count.
 // |+-------- Timer 1 Run Control. 
 // +--------- Timer 1 Overflow Flag
 
 SCON1 = 0x50;
 // XXXX XXXX  Serial Port 0 Control 
 // |||| |||+- Receiver Interrupt Flag
 // |||| ||+-- Transmitter Interrupt Flag
 // |||| |+--- 9th Received Bit State.
 // |||| +---- 9th Transmission Bit State
 // |||+------ Receive Enable.
 // |||     0: Serial Port 0 reception disabled.
 // |||     1: Serial Port 0 received enabled (modes 1,2,and 3). 
 // +++------- Serial Port 0 Mode
 //       000 Synchronous 8bits 12 pCLK
 //       001 Synchronous 8bits 4  pCLK
 //       010 Asynchronous 10 bits,Timer 1 or 2 Baud Rate Equation
 //       011 Valid Stop Required,10 bits,Timer 1 Baud Rate Equation
 //       100 Asynchronous 11 bits 64 pCLK (SMOD = 0), 32 (SMOD = 1)
 //       101 Asynchronous with Multiprocessor Communication.11 bits
 //       110 Asynchronous 11bits Timer 1 or 2 Baud Rate Equation
 //       111 Asynchronous with Multiprocessor Communication.Timer 1 or 2 Baud Rate Equation

 //0010(T1) 0001(T0)
 TMOD  = 0X21
 //9600 8 N 1 BAUD=256-FOSC/384*BAUDRATE
 TH1   = 253;
 P1DDRL = 0x71;   
 //interrupt 
 TI_1  = 0
 RI_1  = 0;
 TR1   = 1
 TR0   = 0;
 ES1   = 1
}

void USART1_ISR(void) interrupt 7 using 2
{     
 if(RI_1)
 {
  if(modbusLayer2.state == E_MODBUS_STATE_RECEVING)
  {
   ModbusCom1StartGapTimer();   
   if(modbusLayer2.index >= MODBUS_BUF_SIZE)
   {
    modbusLayer2.buffer[0] = SBUF1;
    modbusLayer2.index = 0;
   }
   else 
   {
    modbusLayer2.buffer[modbusLayer2.index++] = SBUF1; 
   } 
  }   
  RI_1 = 0;
 }
 else if(TI_1)
 {  
  if(modbusLayer2.state == E_MODBUS_STATE_SENDING)
  {
   if(modbusLayer2.index < modbusLayer2.txLength)
   {
    SBUF1 = modbusLayer2.buffer[modbusLayer2.index++];
   }
   else
   {  
     modbusLayer2.index     = 0;
     modbusLayer2.txLength    = 0;        
     modbusLayer2.state     = E_MODBUS_STATE_RECEVING;                
     MODBUS_COM1_R_ENABLE;
   } 
}
  TI_1 = 0;
}   
}

//发送一个字节,触发发送中断。
void ModbusCom1SendData(INT16U length)
{  
  MODBUS_COM1_R_DISABLE;    
  ES1 = 1;    
  modbusLayer2.txLength  = length;
  modbusLayer2.state     = E_MODBUS_STATE_SENDING;
  modbusLayer2.index     = 1;
  SBUF1 = modbusLayer2.buffer[0];    
}

这个底层是用51单片机实现的,如果是其他单片机,需要实现做些相应的修改就可以了,基本思路是一样的。

应用层

数据关联

回顾之前modbus协议,标准将用户应用数据规划为4张表:

本文以最为常用的0x03、0x10命令进行示例,使用后两种表就可以了。

看到这两条命令,是以16位地址进行索引的,而且有需要与用户应用数据进行关联,怎么做呢?可以设计这样一个数据表:

注:这个示例是很久以前用51单片机实现的,所以int的长度是16位。

typedef unsigned char  BOOLEAN;
typedef unsigned char  INT8U;   
typedef signed   char  INT8S; 
typedef unsigned int   INT16U; 
typedef signed   int   INT16S; 
typedef unsigned long  INT32U;
typedef signed   long  INT32S;
typedef float          FP32; 

enum E_TYPES
{
  ET_U8,     
  ET_U16, 
  ET_U32, 
  ET_FLOAT
};

typedef struct
{

 INT16U  address;
 INT16U  index;
 E_TYPES  type;
}MODBUS_REG_TABLE;
//输入只读寄存器
MODBUS_REG_TABLE code inputRegisterTable[]={
 {10000, IDX_unit,     ET_U8},
 {10001, IDX_temperature, ET_FLOAT},
 {10003, IDX_adc,     ET_U16}
};
//保持寄存器
MODBUS_REG_TABLE code holdingRegisterTable[]={
 {20000, IDX_data_4, ET_FLOAT},
 {20002, IDX_data_5, ET_U8},
 {20003, IDX_data_6, ET_U32}
};

enum E_IDXS
{
  IDX_unit=0,
  IDX_temperature,
  IDX_upperRange,
  IDX_lowerRange,
  IDX_adc,
  IDX_dac_input,
  IDX_dac,
  IDX_dac_upperRange,
  IDX_dac_lowerRange  
};
typedef struct _T_APP_DATA_TABLE
{

  void  *pTarget;
  INT8U length;
}T_APP_DATA_TABLE;
//利用这个表,将分散的数据统一桥接映射
T_APP_DATA_TABLE code appDataTable[]={
 { &tempMeasurent.unit,     1},
 { &tempMeasurent.temperature, 4},
 { &tempMeasurent.upperRange,  4},
 { &tempMeasurent.lowerRange,  4},  
 { &tempMeasurent.adc,      2},
  
 { &dacOutput.input,      4},
 { &dacOutput.dac,       2},
 { &dacOutput.upperRange, 4},
 { &dacOutput.lowerRange, 4}
};
typedef struct _T_MEASURE
{
 
 INT8U  unit;  
 FP32  temperature;
 FP32  upperRange;
 FP32  lowerRange;
 INT16U  adc;  
}T_MEASURE;

typedef struct _T_DAC
{
 
  FP32  input;
  INT16U  dac;
  FP32  upperRange;
  FP32  lowerRange;  
}T_DAC;
extern T_MEASURE xdata tempMeasurent;
extern T_DAC     xdata dacOutput;

为了便于描述,假定有两个应用数据结构体,一个采样当前温度传感器,计算当前温度,并根据设定上下测量范围进行映射;另一个结构体假定需要对外输出一个DA模拟量给别的设备,将输入input值,按照设定范围,用DA通道输出。这些数据在本文中并无实际意义,为了方便描述假设一下。(注:文中关键字xdata,code等为keil C51关键字。忽略即可)

将上述代码,绘制成一张图来分析一下:

首先设计一个索引枚举E_IDXS,枚举值与appDataTable里存放的条目一一对应,appDataTable相当于一个字典,而枚举值则相对于appDataTable数组快速存取的下标。

利用T_APP_DATA_TABLE这个结构体,利用void指针,将任意类型的数据与长度抽象出来,其实这里甚至还可以放入自定义数据类型,比如某一个结构体,只要保证将结构体内存长度填对即可。我为什么这样设计这个结构体呢?因为应用层的模块可能有很多,不同的模块都会有自身的数据,利用这样一个索引字典,就将这些分散的应用数据,整合起来了。再接下来就是水到渠成的事情了,设计一个modbus寄存器表的结构体,其中第一个数据成员address,是modbus报文中的地址;第二个数据成员为index,是应用数据索引;第三个数据type,是该索引对应的数据类型。这样一来,就把modbus寄存器表与应用数据关联起来了。如此一来,要构建只读输入寄存器表,保持寄存器表,甚至什么线圈等表都变成统一模型了。

有了对数据的字典映射管理,只需要实现按寄存器表进行索引,根据不同类型的记录条目进行内存读写操作就可以了。所以需要实现两个读写接口,提供给modbus应用层访问:

static INT8U GetDataFromReg(INT8U * pBuf,INT16U startAddr,INT8U regsNum);
static INT8U StoreDataToReg(INT16U startAddr,INT8U* pBuf,INT8U regsNum,T_MODBUS_LAYER2 *pLayer2);

GetDataFromReg函数就是从寄存器表中,通过查字典,找到对应modbus地址对应的应用数据的内存地址,然后将应用数据从内存拷贝到pBuf所指向的缓冲区,这个缓冲区会进一步封装成应答报文。

StoreDataToReg函数则是从pLayer2中将接收到的报文通过传入寄存器起始地址,查询到寄存器表中的数据索引以及相应的数据类型,从而就只需要实现数据的搬运了。

这里需要注意modbus报文中,字节序是高字节在前,比如地址20000,对应16进制为0x4E20,那么在报文中0x4E先传,0x20后传。

应用框架

链路层本来需要实现帧校验,由于51单片机里资源有限,而接收又采用逐字节中断方式,所以把帧校验放在应用中处理了。先看看

INT8U ModBusLayer7_Interpretervoid )
{
 INT8U xdata frameCode;
 if( modbusLayer2.state == E_MS_PENDING)
 {   
  frameCode = ModBusFrameTypeCheck((T_MODBUS_LAYER2 *)&modbusLayer2,systemPara.modbusAddr); 
  switch( frameCode )
  {       
   case MODBUSRTU_F03:
   if(ModBusRTU_F03_Response((T_MODBUS_LAYER2 *)&modbusLayer2)!=OK)
    modbusLayer2[i].InitiliseLayer2();
   break;
   
   case MODBUSRTU_F10:
   if(ModBusRTU_F10_Response((T_MODBUS_LAYER2 *)&modbusLayer2)!=OK)
    modbusLayer2.InitiliseLayer2();
   break;
   
   //按照这个样式还可以实现其他命令
   default:   
   modbusLayer2.InitiliseLayer2();
   break;
  }  
 }
 return( ERR );
}

INT8U ModBusRTU_F03_Response( T_MODBUS_LAYER2 *pLayer2)
{
  INT8U xdata regs,bytes;
  INT16U xdata regAddr,CRC16Value;

  regAddr = (INT16U)(pLayer2->buffer[2]<<8)+(INT16U)pLayer2->buffer[3]; 
  regs = (INT16U)(pLayer2->buffer[4]<<8)+(INT16U)pLayer2->buffer[5];
  bytes = regs * 2;     
  if( bytes == 0 ) 
  {
     return( ERR );
  }
     
  if( bytes > sizeof(pLayer2->buffer) - 7 ) 
  {
     return( ERR );
  }
            
  GetDataFromReg( &pLayer2->buffer[3], regAddr, regs );    
  pLayer2->buffer[2] = bytes;
    
  CRC16Value = CRC16( pLayer2->buffer, 3+bytes );
  pLayer2->buffer[3+bytes] = (INT8U)( CRC16Value >> 8 );
  pLayer2->buffer[4+bytes] = (INT8U)( CRC16Value );
  pLayer2->SendDataToCom(5+bytes);
  return OK;  
}

INT8U ModBusRTU_F10_Response( T_MODBUS_LAYER2 *pLayer2 )
{
 INT8U xdata bytes,regs;
 INT16U xdata CRC16Value,regAddr;

 regs   = pLayer2->buffer[5];
 bytes  = pLayer2->buffer[6]; 
 regAddr = (INT16U)(pLayer2->buffer[2]<<8)+(INT16U)(pLayer2->buffer[3]);

 if( (bytes == 0) || ( bytes != (regs*2) ) )
 return( ERR );
  
 if(StoreDataToReg( regAddr, &(pLayer2->buffer[7]), regs,pLayer2)!=0)
 {
  pLayer2->buffer[4] = 0x90;
  pLayer2->buffer[5] = 0x01;
 }  
 
 CRC16Value = CRC16( pLayer2->buffer, 6 );
 pLayer2->buffer[6] = ( INT8U )( CRC16Value >> 8 );
 pLayer2->buffer[7] = ( INT8U )( CRC16Value );
 pLayer2->SendDataToCom(8);
 
 return OK;
}

基本思路就是,先判断layer2是否接收一个报文,然后在校验这个报文是否是一个正确的报文,如果是就先进行校验,校验返回值为命令码,再转入相应的命令进行后续处理。

数据校验

帧校验,需要校验CRC-16,这个是必须要做的。除此之外,还需要检查当前请求是否为设备所支持的命令,是否是对该设备的请求。本文没有关注广播报文。CRC-16校验算法就是前文中标准给出的算法。

INT8U ModBusFrameTypeCheck( T_MODBUS_LAYER2 *pLayer2,INT8U address)
{
  INT8U xdata funCode;
  INT8U xdata station;
  if(address!=pLayer2->buffer[0])
     return ERR;
    
  if( pLayer2->index < 8 )
     return( ERR );    

  station = pLayer2->buffer[0];
  funCode = pLayer2->buffer[1];     
  if(station != address)
  {            
    return( ERR );
  }
 
  //0X03命令报文长度为8,
  //0x10命令,第6个字节为寄存器字节数,
  if( (pLayer2->index != 8) && 
     (pLayer2->index != pLayer2->buffer[6]+9) && 
     (pLayer2->index != (5+pLayer2->buffer[2])) ) 
    return( ERR );
    
  if( CRC16(&(pLayer2->buffer[0]), pLayer2->index) != 0 )
  {        
     return( ERR );
  }
  
  switch( funCode )
  {      
     case 0x03:  return( MODBUSRTU_F03 );   
     case 0x10:  return( MODBUSRTU_F10 );                      
     default:     break;
  }    
  return( ERR );
}

总结一下

很久以前用51单片机实现的modbus两条命令,要实现其他的命令或者广播处理,可以类似处理。主要聊一下整体思路。代码很多地方不是很严谨。有兴趣自己实现一下modbus命令,本文可以当个参考。

END

嵌入式从0到1 专注于嵌入式知识分享
评论
  • 美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?‌美国加州CEC能效认证与美国DOE能效认证在多个方面存在显著差异‌。认证范围和适用地区‌CEC能效认证‌:仅适用于在加利福尼亚州销售的电器产品。CEC认证的范围包括制冷设备、房间空调、中央空调、便携式空调、加热器、热水器、游泳池加热器、卫浴配件、光源、应急灯具、交通信号模块、灯具、洗碗机、洗衣机、干衣机、烹饪器具、电机和压缩机、变压器、外置电源、消费类电子设备
    张工nx808593 2025-02-27 18:04 128浏览
  • 更多生命体征指标风靡的背后都只有一个原因:更多人将健康排在人生第一顺位!“AGEs,也就是晚期糖基化终末产物,英文名Advanced Glycation End-products,是存在于我们体内的一种代谢产物” 艾迈斯欧司朗亚太区健康监测高级市场经理王亚琴说道,“相信业内的朋友都会有关注,最近该指标的热度很高,它可以用来评估人的生活方式是否健康。”据悉,AGEs是可穿戴健康监测领域的一个“萌新”指标,近来备受关注。如果站在学术角度来理解它,那么AGEs是在非酶促条件下,蛋白质、氨基酸
    艾迈斯欧司朗 2025-02-27 14:50 424浏览
  • 振动样品磁强计是一种用于测量材料磁性的精密仪器,广泛应用于科研、工业检测等领域。然而,其测量准确度会受到多种因素的影响,下面我们将逐一分析这些因素。一、温度因素温度是影响振动样品磁强计测量准确度的重要因素之一。随着温度的变化,材料的磁性也会发生变化,从而影响测量结果的准确性。因此,在进行磁性测量时,应确保恒温环境,以减少温度波动对测量结果的影响。二、样品制备样品的制备过程同样会影响振动样品磁强计的测量准确度。样品的形状、尺寸和表面处理等因素都会对测量结果产生影响。为了确保测量准确度,应严格按照规
    锦正茂科技 2025-02-28 14:05 154浏览
  •           近日受某专业机构邀请,参加了官方举办的《广东省科技创新条例》宣讲会。在与会之前,作为一名技术工作者一直认为技术的法例都是保密和侵权方面的,而潜意识中感觉法律有束缚创新工作的进行可能。通过一个上午学习新法,对广东省的科技创新有了新的认识。广东是改革的前沿阵地,是科技创新的沃土,企业是创新的主要个体。《广东省科技创新条例》是广东省为促进科技创新、推动高质量发展而制定的地方性法规,主要内容包括: 总则:明确立法目
    广州铁金刚 2025-02-28 10:14 111浏览
  • RGB灯光无法同步?细致的动态光效设定反而成为产品客诉来源!随着科技的进步和消费者需求变化,电脑接口设备单一功能性已无法满足市场需求,因此在产品上增加「动态光效」的形式便应运而生,藉此吸引消费者目光。这种RGB灯光效果,不仅能增强电脑周边产品的视觉吸引力,还能为用户提供个性化的体验,展现独特自我风格。如今,笔记本电脑、键盘、鼠标、鼠标垫、耳机、显示器等多种电脑接口设备多数已配备动态光效。这些设备的灯光效果会随着音乐节奏、游戏情节或使用者的设置而变化。想象一个画面,当一名游戏玩家,按下电源开关,整
    百佳泰测试实验室 2025-02-27 14:15 140浏览
  • 应用趋势与客户需求,AI PC的未来展望随着人工智能(AI)技术的日益成熟,AI PC(人工智能个人电脑)逐渐成为消费者和企业工作中的重要工具。这类产品集成了最新的AI处理器,如NPU、CPU和GPU,并具备许多智能化功能,为用户带来更高效且直观的操作体验。AI PC的目标是提升工作和日常生活的效率,通过深度学习与自然语言处理等技术,实现更流畅的多任务处理、实时翻译、语音助手、图像生成等功能,满足现代用户对生产力和娱乐的双重需求。随着各行各业对数字转型需求的增长,AI PC也开始在各个领域中显示
    百佳泰测试实验室 2025-02-27 14:08 267浏览
  • 构建巨量的驾驶场景时,测试ADAS和AD系统面临着巨大挑战,如传统的实验设计(Design of Experiments, DoE)方法难以有效覆盖识别驾驶边缘场景案例,但这些边缘案例恰恰是进一步提升自动驾驶系统性能的关键。一、传统解决方案:静态DoE标准的DoE方案旨在系统性地探索场景的参数空间,从而确保能够实现完全的测试覆盖范围。但在边缘案例,比如暴露在潜在安全风险的场景或是ADAS系统性能极限场景时,DoE方案通常会失效,让我们看一些常见的DoE方案:1、网格搜索法(Grid)实现原理:将
    康谋 2025-02-27 10:00 258浏览
  • 在2024年的科技征程中,具身智能的发展已成为全球关注的焦点。从实验室到现实应用,这一领域正以前所未有的速度推进,改写着人类与机器的互动边界。这一年,我们见证了具身智能技术的突破与变革,它不仅落地各行各业,带来新的机遇,更在深刻影响着我们的生活方式和思维方式。随着相关技术的飞速发展,具身智能不再仅仅是一个技术概念,更像是一把神奇的钥匙。身后的众多行业,无论愿意与否,都像是被卷入一场伟大变革浪潮中的船只,注定要被这股汹涌的力量重塑航向。01为什么是具身智能?为什么在中国?最近,中国具身智能行业的进
    艾迈斯欧司朗 2025-02-28 15:45 243浏览
  • 1,微软下载免费Visual Studio Code2,安装C/C++插件,如果无法直接点击下载, 可以选择手动install from VSIX:ms-vscode.cpptools-1.23.6@win32-x64.vsix3,安装C/C++编译器MniGW (MinGW在 Windows 环境下提供类似于 Unix/Linux 环境下的开发工具,使开发者能够轻松地在 Windows 上编写和编译 C、C++ 等程序.)4,C/C++插件扩展设置中添加Include Path 5,
    黎查 2025-02-28 14:39 149浏览
  •         近日,广电计量在聚焦离子束(FIB)领域编写的专业著作《聚焦离子束:失效分析》正式出版,填补了国内聚焦离子束领域实践性专业书籍的空白,为该领域的技术发展与知识传播提供了重要助力。         随着芯片技术不断发展,芯片的集成度越来越高,结构也日益复杂。这使得传统的失效分析方法面临巨大挑战。FIB技术的出现,为芯片失效分析带来了新的解决方案。它能够在纳米尺度上对芯片进行精确加工和分析。当芯
    广电计量 2025-02-28 09:15 141浏览
  • Matter 协议,原名 CHIP(Connected Home over IP),是由苹果、谷歌、亚马逊和三星等科技巨头联合ZigBee联盟(现连接标准联盟CSA)共同推出的一套基于IP协议的智能家居连接标准,旨在打破智能家居设备之间的 “语言障碍”,实现真正的互联互通。然而,目标与现实之间总有落差,前期阶段的Matter 协议由于设备支持类型有限、设备生态协同滞后以及设备通信协议割裂等原因,并未能彻底消除智能家居中的“设备孤岛”现象,但随着2025年的到来,这些现象都将得到完美的解决。近期,
    华普微HOPERF 2025-02-27 10:32 241浏览
  • 在物联网领域中,无线射频技术作为设备间通信的核心手段,已深度渗透工业自动化、智慧城市及智能家居等多元场景。然而,随着物联网设备接入规模的不断扩大,如何降低运维成本,提升通信数据的传输速度和响应时间,实现更广泛、更稳定的覆盖已成为当前亟待解决的系统性难题。SoC无线收发模块-RFM25A12在此背景下,华普微创新推出了一款高性能、远距离与高性价比的Sub-GHz无线SoC收发模块RFM25A12,旨在提升射频性能以满足行业中日益增长与复杂的设备互联需求。值得一提的是,RFM25A12还支持Wi-S
    华普微HOPERF 2025-02-28 09:06 166浏览
  • 一、VSM的基本原理震动样品磁强计(Vibrating Sample Magnetometer,简称VSM)是一种灵敏且高效的磁性测量仪器。其基本工作原理是利用震动样品在探测线圈中引起的变化磁场来产生感应电压,这个感应电压与样品的磁矩成正比。因此,通过测量这个感应电压,我们就能够精确地确定样品的磁矩。在VSM中,被测量的样品通常被固定在一个震动头上,并以一定的频率和振幅震动。这种震动在探测线圈中引起了变化的磁通量,从而产生了一个交流电信号。这个信号的幅度和样品的磁矩有着直接的关系。因此,通过仔细
    锦正茂科技 2025-02-28 13:30 106浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦