STM32CubeMX学习笔记(30)——FreeRTOS实时操作系统使用(信号量)

发布者:Tianyun2021最新更新时间:2025-02-12 来源: jianshu关键字:STM32CubeMX  FreeRTOS  实时操作系统  信号量 手机看文章 扫描二维码
随时随地手机看文章

一、FreeRTOS简介

FreeRTOS 是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。FreeRTOS 提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。

FreeRTOS 是用 C 和汇编来写的,其中绝大部分都是用 C 语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,FreeRTOS 结构简洁,可读性很强!最主要的是非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。

最新版本 V9.0.0(2016年),尽管现在 FreeRTOS 的版本已经更新到 V10.4.1 了,但是我们还是选择 V9.0.0,因为内核很稳定,并且网上资料很多,因为 V10.0.0 版本之后是亚马逊收购了FreeRTOS之后才出来的版本,主要添加了一些云端组件,一般采用 V9.0.0 版本足以。

  • FreeRTOS官网:http://www.freertos.org/

  • 代码托管网站:https://sourceforge.net/projects/freertos/files/FreeRTOS/

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”


2. 选择 MCU 和封装


3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置


4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

三、SYS Timebase Source

在 System Core 中选择 SYS ,对 Timebase Source 进行设置,选择 TIM1 作为HAL库的时基(除了 SysTick 外都可以)。

在基于STM32 HAL的项目中,一般需要维护的 “时基” 主要有2个:

  1. HAL的时基,SYS Timebase Source

  2. OS的时基(仅在使用OS的情况下才考虑)

而这些 “时基” 该去如何维护,主要分为两种情况考虑:

  • 裸机运行:
    可以通过 SysTick(滴答定时器)或 (TIMx)定时器 的方式来维护 SYS Timebase Source,也就是HAL库中的 uwTick,这是HAL库中维护的一个全局变量。在裸机运行的情况下,我们一般选择默认的 SysTick(滴答定时器) 方式即可,也就是直接放在 SysTick_Handler() 中断服务函数中来维护。

  • 带OS运行:
    前面提到的 SYS Timebase Source 是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay() 以及作为各种 timeout 的时钟基准。

    在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过 SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick(滴答定时器) 了。

    如果共用SysTick,当我们在CubeMX中选择启用FreeRTOS之后,在生成代码时,CubeMX一定会报如下提示:


强烈建议用户在使用FreeRTOS的时候,不要使用 SysTick(滴答定时器)作为 “HAL的时基”,因为FreeRTOS要用,最好是要换一个!!!如果共用,潜在一定风险。

四、FreeRTOS

4.1 参数配置

在 Middleware 中选择 FREERTOS 设置,并选择 CMSIS_V1 接口版本



CMSIS是一种接口标准,目的是屏蔽软硬件差异以提高软件的兼容性。RTOS v1使得软件能够在不同的实时操作系统下运行(屏蔽不同RTOS提供的API的差别),而RTOS v2则是拓展了RTOS v1,兼容更多的CPU架构和实时操作系统。因此我们在使用时可以根据实际情况选择,如果学习过程中使用STM32F1、F4等单片机时没必要选择RTOS v2,更高的兼容性背后时更加冗余的代码,理解起来比较困难。

在 Config parameters 进行具体参数配置。

Kernel settings:

  • USE_PREEMPTION: Enabled:RTOS使用抢占式调度器;Disabled:RTOS使用协作式调度器(时间片)。

  • TICK_RATE_HZ: 值设置为1000,即周期就是1ms。RTOS系统节拍中断的频率,单位为HZ。

  • MAX_PRIORITIES: 可使用的最大优先级数量。设置好以后任务就可以使用从0到(MAX_PRIORITIES - 1)的优先级,其中0位最低优先级,(MAX_PRIORITIES - 1)为最高优先级。

  • MINIMAL_STACK_SIZE: 设置空闲任务的最小任务堆栈大小,以字为单位,而不是字节。如该值设置为128 Words,那么真正的堆栈大小就是 128*4 = 512 Byte。

  • MAX_TASK_NAME_LEN: 设置任务名最大长度。

  • IDLE_SHOULD_YIELD: Enabled 空闲任务放弃CPU使用权给其他同优先级的用户任务。

  • USE_MUTEXES: 为1时使用互斥信号量,相关的API函数会被编译。

  • USE_RECURSIVE_MUTEXES: 为1时使用递归互斥信号量,相关的API函数会被编译。

  • USE_COUNTING_SEMAPHORES: 为1时启用计数型信号量, 相关的API函数会被编译。

  • QUEUE_REGISTRY_SIZE: 设置可以注册的队列和信号量的最大数量,在使用内核调试器查看信号量和队列的时候需要设置此宏,而且要先将消息队列和信号量进行注册,只有注册了的队列和信号量才会在内核调试器中看到,如果不使用内核调试器的话次宏设置为0即可。

  • USE_APPLICATION_TASK_TAG: 为1时可以使用vTaskSetApplicationTaskTag函数。

  • ENABLE_BACKWARD_COMPATIBILITY: 为1时可以使V8.0.0之前的FreeRTOS用户代码直接升级到V8.0.0之后,而不需要做任何修改。

  • USE_PORT_OPTIMISED_TASK_SELECTION: FreeRTOS有两种方法来选择下一个要运行的任务,一个是通用的方法,另外一个是特殊的方法,也就是硬件方法,使用MCU自带的硬件指令来实现。STM32有计算前导零指令吗,所以这里强制置1。

  • USE_TICKLESS_IDLE: 置1:使能低功耗tickless模式;置0:保持系统节拍(tick)中断一直运行。假设开启低功耗的话可能会导致下载出现问题,因为程序在睡眠中,可用ISP下载办法解决。

  • USE_TASK_NOTIFICATIONS: 为1时使用任务通知功能,相关的API函数会被编译。开启了此功能,每个任务会多消耗8个字节。

  • RECORD_STACK_HIGH_ADDRESS: 为1时栈开始地址会被保存到每个任务的TCB中(假如栈是向下生长的)。

Memory management settings:

  • Memory Allocation: Dynamic/Static 支持动态/静态内存申请

  • TOTAL_HEAP_SIZE: 设置堆大小,如果使用了动态内存管理,FreeRTOS在创建 task, queue, mutex, software timer or semaphore的时候就会使用heap_x.c(x为1~5)中的内存申请函数来申请内存。这些内存就是从堆ucHeap[configTOTAL_HEAP_SIZE]中申请的。

  • Memory Management scheme: 内存管理策略 heap_4。

Hook function related definitions:

  • USE_IDLE_HOOK: 置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子。

  • USE_TICK_HOOK: 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子。

  • USE_MALLOC_FAILED_HOOK: 使用内存申请失败钩子函数。

  • CHECK_FOR_STACK_OVERFLOW: 大于0时启用堆栈溢出检测功能,如果使用此功能用户必须提供一个栈溢出钩子函数,如果使用的话此值可以为1或者2,因为有两种栈溢出检测方法。

Run time and task stats gathering related definitions:

  • GENERATE_RUN_TIME_STATS: 启用运行时间统计功能。

  • USE_TRACE_FACILITY: 启用可视化跟踪调试。

  • USE_STATS_FORMATTING_FUNCTIONS: 与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数prvWriteNameToBuffer()、vTaskList()、vTaskGetRunTimeStats()。

Co-routine related definitions:

  • USE_CO_ROUTINES: 启用协程。

  • MAX_CO_ROUTINE_PRIORITIES: 协程的有效优先级数目。

Software timer definitions:

  • USE_TIMERS: 启用软件定时器。

Interrupt nesting behaviour configuration:

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 中断最低优先级。

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 系统可管理的最高中断优先级。

4.2 创建信号量Semaphore

在 Timers and Semaphores 进行配置。

4.2.1 创建二值信号量Binary Semaphore

  • Semaphore Name: 信号量名称

  • Allocation: 分配方式:Dynamic 动态内存创建

  • Conrol Block Name: 控制块名称

4.2.2 创建计数信号量Counting Semaphore

要想使用计数信号量必须在 Config parameters 中把 USE_COUNTING_SEMAPHORES 选择 Enabled 来使能。



  • Semaphore Name: 信号量名称

  • Count: 计数信号量的最大值

  • Allocation: 分配方式:Dynamic 动态内存创建

  • Conrol Block Name: 控制块名称

4.3 创建任务Task

我们创建两个任务,一个信号量接收任务,一个信号量发送任务。



  • Task Name: 任务名称

  • Priority: 优先级,在 FreeRTOS 中,数值越大优先级越高,0 代表最低优先级

  • Stack Size (Words): 堆栈大小,单位为字,在32位处理器(STM32),一个字等于4字节,如果传入512那么任务大小为512*4字节

  • Entry Function: 入口函数

  • Code Generation Option: 代码生成选项

  • Parameter: 任务入口函数形参,不用的时候配置为0或NULL即可

  • Allocation: 分配方式:Dynamic 动态内存创建

  • Buffer Name: 缓冲区名称

  • Conrol Block Name: 控制块名称

五、KEY

5.1 参数配置

在 System Core 中选择 GPIO 设置。


在右边图中找到按键对应引脚,选择 GPIO_Input。


六、UART串口打印

查看 STM32CubeMX学习笔记(6)——USART串口使用

七、生成代码

输入项目名和项目路径


选择应用的 IDE 开发环境 MDK-ARM V5


每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。


点击 GENERATE CODE 生成代码


八、相关API说明

8.1 osSemaphoreCreate

用于创建一个二值信号量,并返回一个ID。

函数osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
参数semaphore_def: 引用由osSemaphoreDef定义的信号量

count: 信号量数量
返回值成功返回信号量ID,失败返回0

8.2 osSemaphoreDelete

用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。

函数osStatus osSemaphoreDelete (osSemaphoreId semaphore_id)
参数semaphore_id: 信号量ID
返回值错误码

8.3 osSemaphoreRelease

用于释放信号量的宏。释放的信号量对象必须是已经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数 xSemaphoreCreateRecursiveMutex() 创建的递归互斥量。可用在中断服务程序中。

函数osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
参数semaphore_id: 信号量ID
返回值错误码

8.4 osSemaphoreWait

用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。可用在中断服务程序中。

函数int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)
参数semaphore_id: 信号量ID

millisec:等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期)。如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为 portMAX_DELAY ,则任务将一直阻塞在该信号量上(即没有超时时间)
返回值错误码

九、二值信号量

9.1 运作机制


创建信号量时,系统会为创建的信号量对象分配内存,并把可用信号量初始化为用户自定义的个数, 二值信号量的最大可用信号量个数为 1。

二值信号量获取,任何任务都可以从创建的二值信号量资源中获取一个二值信号量,获取成功则返回正确,否则任务会根据用户指定的阻塞超时时间来等待其它任务/中断释放信号量。在等待这段时间,系统将任务变成阻塞态,任务将被挂到该信号量的阻塞等待列表中。

假如某个时间中断/任务释放了信号量,那么,由于获取无效信号量而进入阻塞态的任务将获得信号量并且恢复为就绪态状态。

9.2 阻塞式

这里不知道为什么发送函数里打印字多了就进不到接收函数


/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/

#include 'main.h'

#include 'cmsis_os.h'


/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */

#include

#include

/* USER CODE END Includes */


/* Private typedef -----------------------------------------------------------*/

/* USER CODE BEGIN PTD */


/* USER CODE END PTD */


/* Private define ------------------------------------------------------------*/

/* USER CODE BEGIN PD */


/* USER CODE END PD */


/* Private macro -------------------------------------------------------------*/

/* USER CODE BEGIN PM */


/* USER CODE END PM */


/* Private variables ---------------------------------------------------------*/

UART_HandleTypeDef huart1;

DMA_HandleTypeDef hdma_usart1_rx;

DMA_HandleTypeDef hdma_usart1_tx;


osThreadId defaultTaskHandle;

osThreadId ReceiveHandle;

osThreadId SendHandle;

osSemaphoreId BinarySemHandle;

osSemaphoreId CountSemHandle;

/* USER CODE BEGIN PV */


/* USER CODE END PV */


/* Private function prototypes -----------------------------------------------*/

void SystemClock_Config(void);

static void MX_GPIO_Init(void);

static void MX_DMA_Init(void);

static void MX_USART1_UART_Init(void);

void StartDefaultTask(void const * argument);

void ReceiveTask(void const * argument);

void SendTask(void const * argument);


/* USER CODE BEGIN PFP */


/* USER CODE END PFP */


/* Private user code ---------------------------------------------------------*/

/* USER CODE BEGIN 0 */


/* USER CODE END 0 */


/**

  * @brief  The application entry point.

  * @retval int

  */

int main(void)

{

  /* USER CODE BEGIN 1 */


  /* USER CODE END 1 */


  /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */

  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */

  MX_GPIO_Init();

  MX_DMA_Init();

  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */


  /* USER CODE END 2 */


  /* USER CODE BEGIN RTOS_MUTEX */

  /* add mutexes, ... */

  /* USER CODE END RTOS_MUTEX */


  /* Create the semaphores(s) */

  /* definition and creation of BinarySem */

  osSemaphoreDef(BinarySem);

  BinarySemHandle = osSemaphoreCreate(osSemaphore(BinarySem), 1);


  /* definition and creation of CountSem */

  osSemaphoreDef(CountSem);

  CountSemHandle = osSemaphoreCreate(osSemaphore(CountSem), 5);


  /* USER CODE BEGIN RTOS_SEMAPHORES */

  /* add semaphores, ... */

  /* USER CODE END RTOS_SEMAPHORES */


  /* USER CODE BEGIN RTOS_TIMERS */

  /* start timers, add new ones, ... */

  /* USER CODE END RTOS_TIMERS */


  /* USER CODE BEGIN RTOS_QUEUES */

  /* add queues, ... */

  /* USER CODE END RTOS_QUEUES */


  /* Create the thread(s) */

  /* definition and creation of defaultTask */

  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);

  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);


  /* definition and creation of Receive */

  osThreadDef(Receive, ReceiveTask, osPriorityIdle, 0, 128);

  ReceiveHandle = osThreadCreate(osThread(Receive), NULL);


  /* definition and creation of Send */

  osThreadDef(Send, SendTask, osPriorityIdle, 0, 128);

  SendHandle = osThreadCreate(osThread(Send), NULL);


  /* USER CODE BEGIN RTOS_THREADS */

  /* add threads, ... */

  /* USER CODE END RTOS_THREADS */


  /* Start scheduler */

  osKernelStart();


  /* We should never get here as control is now taken by the scheduler */

[1] [2] [3] [4]
关键字:STM32CubeMX  FreeRTOS  实时操作系统  信号量 引用地址:STM32CubeMX学习笔记(30)——FreeRTOS实时操作系统使用(信号量)

上一篇:STM32CubeMX学习笔记(31)——FreeRTOS实时操作系统使用(互斥量)
下一篇:STM32CubeMX学习笔记(28)——FreeRTOS实时操作系统使用(任务管理)

推荐阅读最新更新时间:2026-03-25 06:57

STM32CubeMX学习笔记(33)——FreeRTOS实时操作系统使用(软件定时器)
一、FreeRTOS简介 FreeRTOS 是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。FreeRTOS 提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。 FreeRTOS 是用 C 和汇编来写的,其中绝大部分都是用 C 语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,FreeRTOS 结构简洁,可读性很强!最主要的是非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。 最新版本 V9.0.0(2016年),尽管现在 FreeRTOS 的版本已经更新到 V10.4.1 了,但是我们还是选择 V9.0.0,因为内核很稳定,并且网上资料很多,因为 V10.0.0 版
[单片机]
STM32实时操作系统FreeRTOS配置指南
一、基础环境配置 CubeMX工程创建 选择正确的STM32型号 在 Middleware 中勾选FREERTOS 选择模式:CMSIS_V1(兼容性更好)或CMSIS_V2(新特性) 时钟配置 确保系统时钟(如72MHz)正确配置 为RTOS心跳时钟选择时间基准: HAL_SYSTICK_Config(HCLK_FREQ / configTICK_RATE_HZ); 二、内核参数配置 在FreeRTOSConfig.h中关键配置项: #define configUSE_PREEMPTION 1 // 启用抢占式调度 #define configCPU_CLOCK_HZ (SystemCore
[单片机]
STM32CubeMXFreeRTOS系统CPU使用率监测测试
1.测试描述: 使用STM32CubeMX自动配置的工程,对其提供的FreeRTOS系统的CPU使用率进行监测,并通过串口打印。 2.测试环境: (1)软件环境:STM32CubeMX-4.22.0,IAR-7.5,串口调试工具 (2)硬件环境:原子战舰V3开发板 3.测试准备: (1)STM32CubeMX软件主要配置: 管脚和其他外设的配置直接省略了,具体的可查看源码文件里的ioc文件。下面贴出主要的系统方面配置图: 首先是Configuration界面,从图中可以看出测试工程使用的东西并不多; 点击中间层的FREERTOS进入系统配置界面,主要注意下图中红色标注的部分;
[单片机]
<font color='red'>STM32CubeMX</font>中<font color='red'>FreeRTOS</font>系统CPU使用率监测测试
Ulog使用硬件RTC时间戳信号量锁死分析
一、现象描述 在使能硬件RTC,初始化阶段未设置时间的情况下【测试环境为4.1.0版本Env创建的Keil工程】 如果开启Ulog时间戳,打印日志导致线程锁死 根本原因为打印时间戳日志的过程中由于未设置时间,导致再次调用日志打印,在以下函数中挂起线程 /* drv_rtc.c 文件 / if (HAL_RTCEx_BKUPRead(&RTC_Handler, RTC_BKP_DR1) != BKUP_REG_DATA) { LOG_I( RTC hasn't been configured, please use command to config. ); / 其他代码省略 */ } 在LOG_I()中再次运行LOG_
[单片机]
Ulog使用硬件RTC时间戳<font color='red'>信号量</font>锁死分析
stm32和同类单片机用ADC采集多通道信号量
在此之前,使用stm32的ADC采集信号,使用到的通道就只有一个,用的时候也是别人写好的代码,没去研究,然而最近用GD32F350单片机作一个电源板时,要采集6个通道的信号,所以就仔细看了手册,有规则通道和注入通道两个概念,按概念,规则通道是按顺序采集,注入通道可以最多设备四个,有独立的保存采集数据的寄存器。如果超过4个就不能没每个每个通道对应一个数据寄存器;如果是规则通道,当通道数大于一时是可以先设置采集通道再读数据,但是我想要的是像手册上说大的一次就把6个通道的数据采集出来,但是规则通道只有一个数据寄存器,如何知道每一次采集的数据是对应哪一个通道呢?最后问了一个做电源有经验的人,他说使用DMA,设置数据长度为6个,然后每个通道
[单片机]
linux驱动学习(3)--同步、信号量和自旋锁
在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源) ,可能会引发“竞态” ,因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用) 。 自旋锁与信号量“类似而不类” ,类似说的是它们功能上的相似性, “不类”指代它们在本质和实现机理上完全不一样,不属于一类。 自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁, “自旋”就是“在原地打转” 。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的“不类” 。 但是,无论是信号量,还
[单片机]
ucos-ii示例2:信号量测试
环境: 主机:WIN8 开发环境:MDK4.72 ucgui版本:3.90 ucos版本:ucos-ii mcu: stm32f103VE 说明: 本示例新建一个信号量,task1接收信号量,task2发送信号量。task1时间片为1s/次,task2时间片3s/次。可以看到,因为信号量的关系,task1输出也是3s/次。 注意: 1.信号量的创建不能在内核初始化之前 源码: #define TASK_STK_SIZE 512 /********************************************************************* * 静态函数 *
[单片机]
ucos-ii示例3:互斥型信号量测试
环境: 主机:WIN8 开发环境:MDK4.72 ucgui版本:3.90 ucos版本:ucos-ii mcu: stm32f103VE 说明: 本示例新建一个互斥型信号量,通过此信号量每次只能有1个任务使用资源函数resource 注意: 1.互斥型信号量的创建不能在内核初始化之前 2.互斥型信号量的请求和释放函数只能在同一个任务中执行,如果一个任务请求了信号量,另一个任务释放信号量的操作会失败 3.为了应对信号量导致的不同任务之间的优先级反转,互斥型信号量的创建需要设置一个优先级,且这个优先级必须是尚未使用的,否则会创建失败 效果图: 源码: #define TASK_STK_SIZE 512 /***
[单片机]
ucos-ii示例3:互斥型<font color='red'>信号量</font>测试
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved