1. 概述

1.1 实验目的

        本实验旨在通过 STM32 微控制器的硬件 SPI 接口,对 W25Q64 外部 NOR Flash 存储芯片进行读写操作。实验演示了芯片ID读取、随机扇区擦除、多页数据写入和扇区数据读取。并提供对应的驱动文件,帮助读者深入理解 STM32 硬件 SPI 通信协议的工作机制,并掌握基于串行 Flash 的数据存储与访问方法。
        通过本实验,读者不仅可以掌握 SPI 接口的基本使用技巧,还能够熟悉 W25Q 系列 Flash 的命令结构、存储特性及应用方法。这对于日后在嵌入式系统开发中涉及外部存储扩展(如固件存储、日志记录、字库管理等)提供了技术积累和实践基础,具有良好的工程指导意义。

1.2 SPI协议介绍

        SPI(Serial Peripheral Interface,串行外设接口)是一种全双工、高速、主从架构的同步串行通信协议。由 Motorola 首创,目前已被广泛应用于微控制器与外设(如 Flash、LCD、传感器、ADC、DAC 等)之间的通信中。SPI 接口结构简单、速率高、实现方便,是嵌入式系统中最常用的通信方式之一。

1.2.1 接口引脚

引脚名称方向(以主机为参考)说明
SCLK主机输出串行时钟信号
MOSI主机输出主出从入(Master Out Slave In)
MISO从机输出主入从出(Master In Slave Out)
CS主机输出片选信号,低电平有效,用于选中从设备

注: 对于多个从机设备,通常采用多个 CS 引脚来分别控制。

1.2.2 工作模式        

        SPI 定义了 4 种工作模式,具体取决于时钟极性(CPOL)和时钟相位(CPHA):

模式CPOLCPHA空闲时钟采样时刻
000第一个边沿(上升沿)
101第二个边沿(下降沿)
210第一个边沿(下降沿)
311第二个边沿(上升沿)

W25Q64 一般工作在 Mode 0(CPOL=0,CPHA=0) 模式下

1.3 W25Q64介绍

        W25Q系列存储芯片凭借其出色的读写速度与大容量存储优势,在嵌入式领域应用广泛,常被用于存储字库信息、LCD显示界面图片、语音播报音频以及固件数据等各类关键内容。

数据类型示例
程序代码Bootloader、主程序固件
字体库/图形数据用于 OLED/LCD 显示的中文字库、位图图标等
参数配置JSON配置、阈值设置、网络参数等
日志/历史数据温湿度采集记录、事件发生时间、设备运行状态等
音频数据播报文件、提示音、报警音

1.3.1 芯片引脚

引脚编号引脚名称引脚用途
1/CS芯片选择,低电平选中芯片进行通信,高电平芯片待机
2DO数据输出,向外部设备输出芯片内部数据
3/WP写保护,低电平禁止芯片写入和擦除操作
4GND接地,提供电气参考地
5DI数据输入,接收外部输入到芯片的命令、地址和数据
6CLK串行时钟,由主设备提供时钟信号同步数据传输
7/HOLD暂停,低电平芯片暂停操作进入低功耗,高电平恢复
8VCC电源,为芯片提供2.7V - 3.6V工作电压

1.3.2 其他型号

W25Q芯片共有如下型号及对应的容量和寻址范围:

型号容量(Mbit)容量(MByte)寻址范围(十六进制)
W25Q4040.50x000000 – 0x07FFFF
W25Q80810x000000 – 0x0FFFFF
W25Q161620x000000 – 0x1FFFFF
W25Q323240x000000 – 0x3FFFFF
W25Q646480x000000 – 0x7FFFFF
W25Q128128160x000000 – 0xFFFFFF
W25Q256256320x000000 – 0x1FFFFFF
W25Q512512640x000000 – 0x3FFFFFF

1.3.3 存储空间

        W25Q64 的地址空间从 0x000000 开始,到 0x7FFFFF 结束,总计 8 × 1024 × 1024 = 8,388,608 个字节地址(即 8MB)。每一个存储单元(地址)表示一个字节(8 bit)。

层级大小数量总大小
芯片8 MByte(64 Mbit)18 MByte
块(Block)64 KB128128 块 × 64 KB = 8 MB
扇区(Sector)4 KB2048128 块 × 16 扇区 × 4 KB = 8 MB
页(Page)256 Bytes32,768128 块 × 16 扇区 × 16 页 × 256B = 8 MB

注:每个 块包含 16 个扇区,每个扇区包含 16 页

1.3.4 指令介绍

常用指令

指令码(Hex)指令名称功能描述备注说明
0x06Write Enable使能写操作(必须在写/擦前调用)写入和擦除前必须调用一次
0x04Write Disable禁用写操作,防止误操作可选调用
0x05Read Status Register 1读取状态寄存器1(SR1)包含 WIP(忙标志)、WEL(写使能)等
0x35Read Status Register 2读取状态寄存器2(SR2)包含 QE(Quad Enable)位
0x01Write Status Registers写入状态寄存器1 和 2设置保护区、启用 Quad 模式等
0x9FRead JEDEC ID读取 JEDEC 厂商/设备 ID返回厂商 ID(EFh)、容量等
0x90Read Manufacturer/Device ID读取制造商和设备 ID需提供 3 字节 Dummy 地址

读写指令

指令码(Hex)指令名称功能描述备注说明
0x03Read Data标准读取(单线 SPI,低速)最常用读取指令
0x0BFast Read快速读取(需一个 Dummy Byte)速率更高
0x6BFast Read Quad Output快速四线读取(Quad SPI 模式)需要启用 QE 位
0x02Page Program页写入(最大 256 字节,页内写)写前需执行 Write Enable

擦除指令

指令码(Hex)指令名称功能描述备注说明
0x20Sector Erase (4KB)擦除 1 个扇区(4KB)最小擦除单位
0x52Block Erase 32KB擦除 1 个 32KB 块可选中等粒度
0xD8Block Erase 64KB擦除 1 个 64KB 块常用于固件清理
0xC70x60Chip Erase擦除整片 Flash时间较长(几秒)

其他指令

指令码(Hex)指令名称功能描述备注说明
0xB9Power Down进入低功耗休眠模式降低待机功耗
0xABRelease Power Down / Read ID释放休眠模式并读取 ID用于唤醒芯片
0xFFDummy Instruction空指令(可用于调试或 SPI 同步)SPI 多字节读取常用填充字节
0x9FRead JEDEC ID厂商ID=0xEF 类型=0x40h 容量=0x17容量(字节) = 2^(容量ID的十进制值) = 2^23 byte

1.3.5 读写流程

读操作:

(1)写入操作前,必须先进行写使能。
(2)每个数据位只能由1改写为0,不能由0改写为1。
(3)写入数据前必须先檫除,檫除后,所有数据位变为1。
(4)擦除必须按最小擦除单元(扇区)进行。
(5)连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。
(6)写入操作结束后,芯片进入忙状态,不响应新的读写操作,需进行等待才能进行后续操作。

写操作:

(1)直接调用读取时序,无需读使能,无需额外操作,没有页的限制。
(2)读取操作结束后不会进入忙状态,但不能在忙状态时读取。

1.3.6 地址对齐

分类大小对齐要求(起始地址必须满足)编号计算方式起始地址计算方式
页(Page)256 字节addr % 256 == 0page = addr / 256pageStartAddr = page * 256
扇区(Sector)4KB(4096 字节)addr % 4096 == 0sector = addr / 4096sectorStartAddr = sector * 4096
块(Block)64KB(65536 字节)addr % 65536 == 0block = addr / 65536blockStartAddr = block * 65536

2 STM32CubeMx

2.1 SYS设置

2.2 RCC设置

2.3 USART设置

2.4 GPIO设置

用于片选设备引脚,与w25q64的片选CS引脚相连,配置为输出,默认高电平即可

2.5 SPI设置

        黄色感叹号是提示SPI引脚被占用,是因为作者把PB12引脚配置成了通用输出连接w25q64芯片的片选引脚,PB12引脚同时也是复用引脚,用作当STM32作为从机时SPI的NSS片选引脚,相当于w25q64芯片的CS引脚。我们这里是把STM32作为主机,所以这个复用硬件占用不影响实验目的。

2.6 project配置

配置完毕后,点击右上角的GENERATE CODE即可生成代码,并打开工程,在 keil MDK中进行驱动文件的添加和相关工程的配置。

3 keil MDK配置

3.1 debug配置

3.2 新增驱动文件

点击新建后,CTRL+S 进行保存,存放路径和文件名如下图所示,hardware是作者在Core目录下新建的一个文件夹:

3.3 构建工程目录

3.4 构建工程文件

3.5 引入依赖包

这一步是为了引入一个依赖包,在调试过程中,使用串口打印重定向,需要这个依赖包

4 VSCode

4.1 驱动文件

#ifndef __W25Q64_H
#define __W25Q64_H

#include "stm32f1xx_hal.h"
#include "gpio.h"

// ==== SPI 与引脚定义 ====
// 在 main.c 或其他初始化文件中定义实际使用的 SPI 句柄和 CS 引脚
#define W25Q64_SPI         hspi2
#define W25Q64_CS_GPIO     W25Q64_CS_GPIO_Port
#define W25Q64_CS_PIN      W25Q64_CS_Pin

// ==== 指令码定义 ====
#define W25Q64_CMD_WRITE_ENABLE     0x06
#define W25Q64_CMD_WRITE_DISABLE    0x04
#define W25Q64_CMD_READ_STATUS1     0x05
#define W25Q64_CMD_READ_DATA        0x03
#define W25Q64_CMD_PAGE_PROGRAM     0x02
#define W25Q64_CMD_SECTOR_ERASE     0x20
#define W25Q64_CMD_CHIP_ERASE       0xC7
#define W25Q64_CMD_READ_ID          0x9F

// ==== 容量参数 ====
#define W25Q64_SECTOR_SIZE          4096
#define W25Q64_PAGE_SIZE            256
#define W25Q64_TOTAL_SIZE           (8 * 1024 * 1024) // 8MB

// ==== 函数声明 ====
uint32_t W25Q64_ReadID(void);
void     W25Q64_WriteEnable(void);
uint8_t  W25Q64_ReadStatus1(void);
void     W25Q64_WaitBusy(void);
void     W25Q64_ReadData(uint32_t addr, uint8_t* buf, uint32_t len);
void     W25Q64_PageProgram(uint32_t addr, const uint8_t* buf, uint32_t len);
void     W25Q64_SectorErase(uint32_t addr);
void     W25Q64_ChipErase(void);

#endif
#include "w25q64.h"

extern SPI_HandleTypeDef W25Q64_SPI;

// === 片选控制 ===
static void W25Q64_CS_LOW(void) {
    HAL_GPIO_WritePin(W25Q64_CS_GPIO, W25Q64_CS_PIN, GPIO_PIN_RESET);
}
static void W25Q64_CS_HIGH(void) {
    HAL_GPIO_WritePin(W25Q64_CS_GPIO, W25Q64_CS_PIN, GPIO_PIN_SET);
}

// === 写使能 ===
void W25Q64_WriteEnable(void) {
    uint8_t cmd = W25Q64_CMD_WRITE_ENABLE;
    W25Q64_CS_LOW();
    HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
    W25Q64_CS_HIGH();
}

// === 读取状态寄存器1 ===
uint8_t W25Q64_ReadStatus1(void) {
    uint8_t cmd = W25Q64_CMD_READ_STATUS1;
    uint8_t status;
    W25Q64_CS_LOW();
    HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
    HAL_SPI_Receive(&W25Q64_SPI, &status, 1, HAL_MAX_DELAY);
    W25Q64_CS_HIGH();
    return status;
}

// === 等待空闲(WIP=0) ===
void W25Q64_WaitBusy(void) {
    while (W25Q64_ReadStatus1() & 0x01);
}

// === 读取 JEDEC ID ===
uint32_t W25Q64_ReadID(void) {
    uint8_t cmd = W25Q64_CMD_READ_ID;
    uint8_t id_buf[3];
    W25Q64_CS_LOW();
    HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
    HAL_SPI_Receive(&W25Q64_SPI, id_buf, 3, HAL_MAX_DELAY);
    W25Q64_CS_HIGH();
    return (id_buf[0] << 16) | (id_buf[1] << 8) | id_buf[2];
}

// === 读取数据 ===
void W25Q64_ReadData(uint32_t addr, uint8_t* buf, uint32_t len) {
    uint8_t cmd[4];
    cmd[0] = W25Q64_CMD_READ_DATA;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;
    
    W25Q64_CS_LOW();
    HAL_SPI_Transmit(&W25Q64_SPI, cmd, 4, HAL_MAX_DELAY);
    HAL_SPI_Receive(&W25Q64_SPI, buf, len, HAL_MAX_DELAY);
    W25Q64_CS_HIGH();
}

// === 页编程(最大 256 字节,不能跨页) ===
void W25Q64_PageProgram(uint32_t addr, const uint8_t* buf, uint32_t len) {
    if (len > W25Q64_PAGE_SIZE) len = W25Q64_PAGE_SIZE;

    W25Q64_WriteEnable();

    uint8_t cmd[4];
    cmd[0] = W25Q64_CMD_PAGE_PROGRAM;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    W25Q64_CS_LOW();
    HAL_SPI_Transmit(&W25Q64_SPI, cmd, 4, HAL_MAX_DELAY);
    HAL_SPI_Transmit(&W25Q64_SPI, (uint8_t*)buf, len, HAL_MAX_DELAY);
    W25Q64_CS_HIGH();

    W25Q64_WaitBusy();
}

// === 扇区擦除(按4KB) ===
void W25Q64_SectorErase(uint32_t addr) {
    W25Q64_WriteEnable();

    uint8_t cmd[4];
    cmd[0] = W25Q64_CMD_SECTOR_ERASE;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    W25Q64_CS_LOW();
    HAL_SPI_Transmit(&W25Q64_SPI, cmd, 4, HAL_MAX_DELAY);
    W25Q64_CS_HIGH();

    W25Q64_WaitBusy();
}

// === 整片擦除 ===
void W25Q64_ChipErase(void) {
    W25Q64_WriteEnable();

    uint8_t cmd = W25Q64_CMD_CHIP_ERASE;
    W25Q64_CS_LOW();
    HAL_SPI_Transmit(&W25Q64_SPI, &cmd, 1, HAL_MAX_DELAY);
    W25Q64_CS_HIGH();

    W25Q64_WaitBusy();
}

4.2 主函数

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "w25q64.h"
#include <stdlib.h>   // 提供 rand(), srand()
#include <string.h>   // 提供 strlen()
/* 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 ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
static uint8_t read_buffer[W25Q64_SECTOR_SIZE];  // 避免栈溢出
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* 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_USART1_UART_Init();
  MX_SPI2_Init();
  /* USER CODE BEGIN 2 */
  

  printf("W25Q64 ID Read Test\r\n");
  // 1.1 读取W25Q64芯片ID
  uint32_t chipID = W25Q64_ReadID();

  // 1.2 解析ID(W25Q64的ID是3字节:0xEF 0x40 0x17)
  uint8_t manufacturer_id = (chipID >> 16) & 0xFF;  // 第1字节:厂商ID(0xEF)
  uint8_t memory_type = (chipID >> 8) & 0xFF;       // 第2字节:内存类型(0x40)
  uint8_t capacity_id = chipID & 0xFF;              // 第3字节:容量ID(0x17)

  // 1.3 计算实际容量(单位:字节)
  uint32_t capacity_bytes = (1 << capacity_id);     // 2^capacity_id(例如 0x17=23 → 2^23=8MB)

  // 1.4 打印结果
  printf("Manufacturer ID: 0x%02X\r\n", manufacturer_id);  // 应为0xEF(Winbond)
  printf("Memory Type: 0x%02X\r\n", memory_type);          // 应为0x40(SPI Flash)
  printf("Capacity ID: 0x%02X\r\n", capacity_id);          // 应为0x17(64Mbit/8MB)
  printf("Full ID: 0x%02X 0x%02X 0x%02X\r\n", 
        manufacturer_id, memory_type, capacity_id);
  printf("Calculated Capacity: %lu bytes (%lu Mbit)\r\n", 
        capacity_bytes, capacity_bytes * 8 / (1024 * 1024));

  
  printf("W25Q64 Flash Sector Test\r\n");
  // 2.1 随机定义扇区编号(0 ~ 2047,因为 W25Q64 有 2048 个扇区)
  uint32_t num = 1223;
  uint32_t sector_num = num % 2048;  // 随机扇区号
  printf("Selected random sector: %lu\r\n", sector_num);

  // 2.2 计算扇区的起始地址(扇区号 * 4KB)
  uint32_t sector_addr = sector_num * W25Q64_SECTOR_SIZE; // 扇区大小(4KB)
  printf("Sector start address: 0x%06lX\r\n", sector_addr);

  // 2.3 擦除目标扇区(4KB)
  printf("Erasing sector at 0x%06lX...\r\n", sector_addr);
  W25Q64_SectorErase(sector_addr);
  W25Q64_WaitBusy();  // 等待擦除完成

  // 2.4 写入测试字符串到扇区起始地址
  printf("Writing test string to sector...\r\n");
  char data_string[W25Q64_PAGE_SIZE] = {0};// 定义可变字符串缓冲区(最大长度只能写一页 即 256 字节)
  snprintf(data_string, sizeof(data_string), 
         "the fire page context: \r\n page 1, this is a w25q64 write data test, i can write 256 bytes in this page \r\n");
  W25Q64_PageProgram(sector_addr, (uint8_t *)data_string, strlen(data_string)); // 写入第一页
  W25Q64_WaitBusy();  // 等待写入完成
  memset(data_string, 0, sizeof(data_string));  // 清零
  snprintf(data_string, sizeof(data_string), 
         "the second page context: \r\n page 2, this is a w25q64 write data test, i can write 256 bytes in this page \r\n");
  W25Q64_PageProgram(sector_addr + W25Q64_PAGE_SIZE, (uint8_t *)data_string, strlen(data_string)); // 写入第二页
  W25Q64_WaitBusy();  // 等待写入完成

  // 2.5 读取整个扇区内容
  // uint8_t read_buffer[W25Q64_SECTOR_SIZE] = {0};
  printf("Reading sector content...\r\n");
  W25Q64_ReadData(sector_addr, read_buffer, W25Q64_SECTOR_SIZE);

  // 2.6 打印读出的数据(以字符串形式,仅打印前 4页内容,即1024字节 避免刷屏)
  printf("Read Data (as string, first 256 bytes):\r\n");
  for (int i = 0; i < 1024; i++) {
      char c = read_buffer[i];
      printf("%c", c);
  }
  printf("\r\n");

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    printf("go on");
    HAL_Delay(3000);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

5. 实验验证

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐