【物联网学习笔记】OLED

前言

本文是本人备赛物联网赛项的学习笔记,主要供本人学习、复习,不是经验分享或教学,若有错误,大佬轻喷。

一、前置准备

1. 硬件准备

  • STM32 开发板(本文以蓝桥杯 CT127C 物联网开发板为例,其他 STM32 开发板仅引脚和芯片型号有差异,流程通用)
  • 0.91 寸 I2C 接口 OLED 显示屏(分辨率 128*32,本文配套驱动)
  • MicroUSB 数据线(用于电脑和开发板通信、下载程序)
  • 电脑一台

2. 软件准备

  • STM32CubeMX(用于图形化配置工程,自动生成初始化代码)
  • Keil MDK5(用于编写代码、编译和下载程序)
  • 串口助手(可选,后续拓展实验用)

3. 必看小白避坑提示

  1. 工程保存路径绝对不能有中文、空格、特殊符号,否则一定会编译报错!
  2. OLED 屏 VCC 请接 3.3V,不要接 5V,大概率会烧屏!
  3. 所有代码复制时,不要漏了大括号、分号,C 语言对语法要求极严格。

二、硬件原理与接线详解

1. 基础认知

我们用的 0.91 寸 OLED 是I2C 通信接口,仅需 2 根信号线 + 2 根电源线就能驱动,相比 SPI 接口接线更少,对新手极度友好。

  • I2C 通信有两根核心线:SCL(串行时钟线)、SDA(串行数据线)
  • 本实验中,STM32 和 OLED 的引脚是固定映射的,接线绝对不能接反!

2. 引脚对应接线表

STM32 开发板引脚 OLED 屏引脚 引脚作用
3.3V/VDD VCC 电源正极
GND GND 电源地
PB6 SCL I2C 时钟线
PB7 SDA I2C 数据线

三、STM32CubeMX 图形化配置,点点鼠标就完成

这一步是新手最容易懵的环节,我拆成 6 个步骤,你跟着点就行,不用死记原理,先跑通实验再深究!

步骤 1:新建 CubeMX 工程,选择对应芯片

  1. 打开 STM32CubeMX 软件,点击首页的「ACCESS TO MCU SELECTOR」,进入芯片选择界面;
  2. 左上角搜索框输入你的开发板芯片型号(CT127C 开发板对应STM32WL55JCI7),在右侧结果中选中该芯片;
  3. 点击右上角「Start Project」,创建工程。

步骤 2:配置系统核心时钟

  1. 点击顶部「Clock Configuration」选项卡,进入时钟配置界面;
  2. 按以下参数配置(CT127C 开发板专用,其他开发板可根据芯片手册调整):
    • 振荡器配置:开启 LSE(外部低速时钟,给 RTC 用)、MSI 内部时钟,MSI 范围选择RANGE_11(48MHz)
    • 系统时钟源:SYSCLK 选择 MSI;
    • 分频器:AHB、APB1、APB2 分频全部设为 1,最终系统时钟为 48MHz;
  3. 配置完成后,界面无红色报错即可。

步骤 3:配置 I2C 外设(OLED 驱动核心)

  1. 回到顶部「Pinout & Configuration」选项卡,左侧「Categories」→「Peripherals」,找到I2C1
  2. 点击 I2C1,模式(Mode)选择「I2C」,此时你会发现开发板的 PB6、PB7 引脚自动被配置为 I2C1_SCL、I2C1_SDA,引脚变成绿色;
  3. 下方参数配置保持默认即可:
    • 地址模式:7 位地址模式
    • Timing 参数:0x10805D88
    • 模拟滤波器、数字滤波器保持默认开启状态
  4. 无需手动修改 PB6、PB7 的引脚配置,CubeMX 会自动完成。

【配图 5:CubeMX 中 I2C1 配置界面截图,标注模式选择位置和自动映射的引脚】【配图 6:CubeMX 主界面引脚视图截图,标注 PB6、PB7 变成绿色后的高亮状态】

步骤 4:配套外设配置(RTC + 串口 + GPIO)

因为我们的实验要实现 RTC 时钟读取、按键计数,需要额外配置 3 个外设,同样点点鼠标就完成:

  1. RTC 配置:左侧找到 RTC,勾选「Activate Clock Source」激活时钟源,「Activate Calendar」激活日历功能,其他参数保持默认;
  2. USART2 配置:左侧找到 USART2,模式选择「Asynchronous」异步模式,波特率默认 115200,其他参数保持默认;
  3. DMA 配置:在 USART2 配置界面,点击「DMA Settings」,添加 USART2_RX 的 DMA 接收,模式选择 Normal,其他默认;
  4. GPIO 按键配置:确认 ASW1、ASW2 对应的 GPIO 引脚为输入模式,下拉 / 上拉根据开发板原理图配置(CT127C 开发板默认浮空输入即可)。

步骤 5:工程生成设置

  1. 点击顶部「Project Manager」选项卡,进入工程设置界面;
  2. 「Project」栏目:
    • Project Name:给工程起个名字,比如「OLED_Demo」(不能有中文)
    • Project Location:选择工程保存路径(不能有中文、空格)
    • Toolchain/IDE:选择「MDK-ARM」,版本选 V5
  3. 「Code Generator」栏目:
    • 核心勾选:Generate peripheral initialization as a pair of .c/.h files per peripheral(每个外设生成独立的.c/.h 文件,方便管理)
    • 其他保持默认即可。

步骤 6:生成工程

点击右上角的「GENERATE CODE」按钮,等待工程生成。生成完成后,会弹出提示框,点击「Open Project」,就能自动打开 Keil MDK 工程了。

四、Keil MDK 代码编写与移植,复制粘贴也能成功

工程打开后,我们需要添加 OLED 驱动文件,封装自己的功能函数,全程我会给出完整代码,你直接复制替换就行。

步骤 1:添加驱动文件到工程目录

首先我们需要 4 个核心驱动文件:font.h(字库文件)、oled.h(OLED 驱动头文件)、oled.c(OLED 驱动核心实现)、app.happ.c(我们自己封装的功能函数)。

  1. 打开你的工程文件夹,进入Core/Src文件夹,把oled.capp.c文件放进去;
  2. 进入Core/Inc文件夹,把font.holed.happ.h文件放进去;
  3. 回到 Keil 工程,左侧 Project 窗口,双击Core/Src,点击「Add Files」,把刚才放进去的oled.capp.c添加到工程中,点击 Add 确认。

步骤 2:逐个文件编写完整代码

下面我给出每个文件的完整代码,带详细注释,你直接打开对应文件,全选复制替换即可。

1. font.h

这个文件是 OLED 显示字符的点阵字库,包含 68 和 816 两种字号的 ASCII 字符,赛方会提供直接导入inc文件夹即可

2. oled.h

赛方会提供直接导入inc文件夹即可

3. oled.c

赛方会提供直接导入inc文件夹即可

4. app.h(功能函数头文件,替换原 zsdz.h,直接复制)
cpp 复制代码
#ifndef __APP_H
#define __APP_H

#include "string.h"
#include "stdio.h"
#include "gpio.h"
#include "oled.h"

// 通用状态宏定义
#define APP_ON			1
#define APP_OFF			2
#define APP_TO			3

// OLED显示行号定义(0.91寸128*32屏,共4行,16号字体占2行)
#define APP_OLED_LINE1			0
#define APP_OLED_LINE2			2

// RTC时间结构体
typedef struct
{
unsigned char Year;
unsigned char Month;
unsigned char WeekDay;
unsigned char Date;
unsigned char Hours;
unsigned char Minutes;
unsigned char Seconds;
}RTC_APP;

// 全局变量声明
extern RTC_APP	rtc_value;

// 功能函数声明
void app_write_AL(unsigned short int ALx,unsigned char state);
unsigned char app_read_AL(unsigned short int ALx);
unsigned char app_read_ASW(GPIO_TypeDef *ASWx);
void app_tx_UART(const unsigned char *Data);
void app_UART_IDLE_rx(void);
void app_UART_rx_loop(void);
void app_UART_rx_deal(void);
void app_read_RTC(void);
void OLED_Write(unsigned char Type, unsigned char Data);
void app_write_OLED(unsigned char Lin, unsigned char *Data);
void app_init(void);

#endif
5. app.c(功能函数实现,替换原 zsdz.c,直接复制)
cpp 复制代码
#include "app.h"

// 串口DMA接收缓存区配置
#define g_u8uart_rx_buffer_len  100   // 最大接收长度
RTC_APP	rtc_value; // RTC时间全局变量
unsigned char g_u8uart_rx_len;  // 一帧数据接收长度
unsigned char g_u8uart_rx_end_flag; // 接收完成标志位
unsigned char g_u8uart_rx_buffer[g_u8uart_rx_buffer_len];  // 接收缓存数组

//函数名:app_write_AL
//作用:操作LED的状态
//形参:ALx:LED引脚;state:LED状态
//返回值:无
void app_write_AL(unsigned short int ALx,unsigned char state)
{
switch (state)
{
case APP_ON:
HAL_GPIO_WritePin(GPIOB, ALx, GPIO_PIN_RESET);
break;
case APP_OFF:
HAL_GPIO_WritePin(GPIOB, ALx, GPIO_PIN_SET);
break;
case APP_TO:
HAL_GPIO_TogglePin(GPIOB, ALx);
break;
default:
break;
}
}

//函数名:app_read_AL
//作用:读取LED的状态
//形参:ALx:LED引脚
//返回值:LED的状态
unsigned char app_read_AL(unsigned short int ALx)
{
unsigned char state=HAL_GPIO_ReadPin(GPIOB, ALx);
if(state == GPIO_PIN_SET)
return APP_OFF;
else
return APP_ON;
}

//函数名:app_read_ASW
//作用:读取按键的状态(带消抖)
//形参:ASWx:按键引脚
//返回值:按键的状态
unsigned char app_read_ASW(GPIO_TypeDef *ASWx)
{
unsigned char state = APP_OFF;
if(HAL_GPIO_ReadPin(ASWx,GPIO_PIN_8) == GPIO_PIN_RESET)
{
HAL_Delay(20); // 消抖延时
if(HAL_GPIO_ReadPin(ASWx,GPIO_PIN_8) == GPIO_PIN_RESET)
{
state = APP_ON;
while(HAL_GPIO_ReadPin(ASWx,GPIO_PIN_8) == GPIO_PIN_RESET); // 等待按键松开
}
}
return state;
}

//函数名:app_tx_UART
//作用:串口发送数据
//形参:Data:要发送的数据
//返回值:无
void app_tx_UART(const unsigned char *Data)
{
extern UART_HandleTypeDef huart2;
HAL_UART_Transmit(&huart2,Data,strlen((const char *)Data),strlen((const char *)Data));
}

//函数名:fputc
//作用:printf重定向,用于串口调试
int fputc(int ch,FILE *f)
{
extern UART_HandleTypeDef huart2;
HAL_UART_Transmit(&huart2,(unsigned char*)&ch,1,1);
return 0;
}

//函数名:app_UART_IDLE_rx
//作用:利用IDLE中断判断串口数据是否接收完成
//形参:无
//返回值:无
void app_UART_IDLE_rx(void)
{
extern UART_HandleTypeDef huart2;
extern DMA_HandleTypeDef hdma_usart2_rx;
if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE)==SET)//idle标志位被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除idle标志位
HAL_UART_DMAStop(&huart2); //停止DMA传输
g_u8uart_rx_len=g_u8uart_rx_buffer_len -__HAL_DMA_GET_COUNTER(&hdma_usart2_rx);//计算接收数据长度
g_u8uart_rx_end_flag=1;//置位接收完成标志
}
}

//函数名:app_UART_rx_loop
//作用:串口接收循环处理
//形参:无
//返回值:无
void app_UART_rx_loop(void)
{
extern UART_HandleTypeDef huart2;
if(g_u8uart_rx_end_flag)//如果串口接收完成
{
// 串口数据处理函数
app_UART_rx_deal();
// 清除标志位,重新开启DMA接收
g_u8uart_rx_len=0;
g_u8uart_rx_end_flag=0;
HAL_UART_Receive_DMA(&huart2,g_u8uart_rx_buffer,g_u8uart_rx_buffer_len);
}
}

//函数名:app_UART_rx_deal
//作用:串口数据回环处理(收到什么发回什么)
//形参:无
//返回值:无
void app_UART_rx_deal(void)
{
extern UART_HandleTypeDef huart2;
HAL_UART_Transmit(&huart2,g_u8uart_rx_buffer,g_u8uart_rx_len,g_u8uart_rx_len);
}

//函数名:app_read_RTC
//作用:读取RTC实时时间
//形参:无
//返回值:无
void app_read_RTC(void)
{
extern RTC_HandleTypeDef hrtc;
RTC_DateTypeDef data_value;
RTC_TimeTypeDef time_value;
// 必须先读时间,再读日期,否则会出错
HAL_RTC_GetTime(&hrtc,&time_value,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&data_value,RTC_FORMAT_BIN);
// 赋值到全局结构体
rtc_value.Year = data_value.Year;
rtc_value.Month = data_value.Month;
rtc_value.WeekDay = data_value.WeekDay;
rtc_value.Date = data_value.Date;
rtc_value.Hours = time_value.Hours;
rtc_value.Minutes = time_value.Minutes;
rtc_value.Seconds = time_value.Seconds;
}

//函数名:OLED_Write
//作用:OLED底层I2C写函数(核心)
//形参:Type:命令/数据类型;Data:要写入的内容
//返回值:无
void OLED_Write(unsigned char Type, unsigned char Data)
{
extern I2C_HandleTypeDef hi2c1;
unsigned char pData[2];
pData[0] = Type;
pData[1] = Data;
// I2C主机发送函数:I2C句柄、OLED设备地址、数据、长度、超时时间
HAL_I2C_Master_Transmit(&hi2c1, 0x78, pData, 2, 10);
}

//函数名:app_write_OLED
//作用:OLED字符串显示封装函数,简化调用
//形参:Lin:显示行号;Data:要显示的字符串
//返回值:无
void app_write_OLED(unsigned char Lin, unsigned char *Data)
{
OLED_ShowString(0,Lin,Data,16); // 固定使用16号字体,x坐标0
}

//函数名:app_init
//作用:外设初始化总函数,main函数中调用
//形参:无
//返回值:无
void app_init(void)
{
extern UART_HandleTypeDef huart2;
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); // 使能串口IDLE中断
HAL_UART_Receive_DMA(&huart2,g_u8uart_rx_buffer,g_u8uart_rx_buffer_len);  // 开启串口DMA接收
OLED_Init(); // OLED初始化
}
6. main.c(主函数,替换原有内容,直接复制)
cpp 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 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 "dma.h"
#include "i2c.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "app.h"
#include "oled.h"
/* 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 */
/* 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_DMA_Init();
  MX_USART2_UART_Init();
  MX_RTC_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
	// 我们的功能初始化,必须放在外设初始化之后
	app_init();
	
	unsigned char oled_buffer[16]; // OLED显示缓存数组
	unsigned char key_count = 0;   // 按键计数变量
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {	
		// 1. 读取RTC实时时间
		app_read_RTC();
		// 格式化时间字符串,显示在OLED第一行
		sprintf((char *)oled_buffer,"RTC:%d:%d:%d",rtc_value.Hours,rtc_value.Minutes,rtc_value.Seconds);
		app_write_OLED(APP_OLED_LINE1,oled_buffer);
	
		// 2. 读取按键状态,按下一次计数+1
		if(app_read_ASW(ASW1_GPIO_Port) == APP_ON)	key_count++;
		if(app_read_ASW(ASW2_GPIO_Port) == APP_ON)	key_count++;
		
		// 格式化按键计数字符串,显示在OLED第二行
		sprintf((char *)oled_buffer,"KEY:%d",key_count);
		app_write_OLED(APP_OLED_LINE2,oled_buffer);

    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

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

  /** Configure LSE Drive Capability
  */
  HAL_PWR_EnableBkUpAccess();
  __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW);
  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_LSE
                              |RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_11;
  RCC_OscInitStruct.LSIDiv = RCC_LSI_DIV1;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure the SYSCLKSource, HCLK, PCLK1 and PCLK2 clocks dividers
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK3|RCC_CLOCKTYPE_HCLK
                              |RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1
                              |RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.AHBCLK3Divider = RCC_SYSCLK_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 */

步骤 3:开启 MicroLIB 库(必做!否则 sprintf 会报错)

这是新手 90% 会踩的坑!使用 sprintf 格式化字符串,必须开启 MicroLIB 库:

  1. 点击 Keil 上方的魔术棒按钮(Options for Target);
  2. 进入「Target」选项卡,勾选下方的「Use MicroLIB」;
  3. 点击 OK 确认。

五、工程编译与程序下载

1. 工程编译

  1. 点击 Keil 上方的「Build」按钮(图标是两个向下的箭头),或者直接按键盘 F7;
  2. 等待编译完成,看下方 Build Output 窗口,如果显示0 Error(s), 0 Warning(s),就说明编译完美成功!

2. 程序下载到开发板

  1. 用 MicroUSB 线连接开发板和电脑,确保开发板上电,驱动正常安装;
  2. 点击 Keil 上方的「Download」按钮(图标是 Flash),或者直接按键盘 F8;
  3. 等待下载完成,下方提示「Program Done」,就说明程序已经成功下载到开发板里了!

【配图 13:Keil 下载按钮位置截图,标注下载按钮】【配图 14:下载成功后的提示窗口截图】

六、实验现象与效果展示

程序下载完成后,你会看到:

  1. OLED 屏第一行实时显示 RTC 时间,格式为RTC:时:分:秒,秒数每秒自动跳动更新;
  2. 第二行显示按键按下的次数KEY:数字,每按一次开发板上的 ASW1 或 ASW2 按键,数字就会加 1。

【配图 15:OLED 屏实验现象实拍图,标注第一行的实时时间和第二行的按键计数】

自定义显示内容小技巧

想让 OLED 显示你想要的内容?超简单!

  • 修改sprintf里的双引号内容,就能改显示的文字;
  • 修改app_write_OLED的第一个参数,就能改显示的行号(可选 0、2,对应第一行、第二行);
  • 比如想显示你的昵称,就加一行:sprintf((char *)oled_buffer,"Hello 小白!"); app_write_OLED(0,oled_buffer);
相关推荐
YuanDaima20482 小时前
解决Conda环境下RTX 50系列显卡PyTorch+Transformers+PEFT微调报错
人工智能·pytorch·笔记·python·深度学习·机器学习·conda
三佛科技-134163842122 小时前
融蜡机方案,脱毛热蜡机MCU控制方案开发
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
三佛科技-134163842122 小时前
智能小夜灯方案,智能遥控台灯方案开发MCU控制方案设计
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
誰能久伴不乏2 小时前
给开发板装上嘴巴与耳朵:i.MX6ULL 裸机串口 (UART) 驱动终极指南
arm开发·c++·单片机·嵌入式硬件·arm
wdfk_prog2 小时前
MCU内核电压不稳导致程序跑飞的现象、原因与影响
数据库·单片机·嵌入式硬件
sensen_kiss2 小时前
CPT306 Principles of Computer Games Design 电脑游戏设计原理 Pt.5 VR 游戏
学习·游戏·vr
AnalogElectronic2 小时前
uniapp学习8,电动车充电小程序
学习·小程序·uni-app
郑同学zxc3 小时前
Claude Code 的学习笔记
人工智能·笔记·学习