手撕I2C和SPI协议实现
手撕I2C和SPI协议实现
目录
- I2C协议原理
- I2C位操作实现
- I2C驱动代码编写
- SPI协议原理
- SPI位操作实现
- SPI驱动代码编写
I2C协议原理
I2C(Inter-Integrated Circuit)是一种串行通信总线,使用两根线:SCL(时钟线)和SDA(数据线)。
基本特性
- 主从架构
- 双向半双工通信
- 每个设备都有唯一地址
- 支持多主设备
- 通信速率通常为100kHz(标准模式)、400kHz(快速模式)或1MHz以上(高速模式)
信号状态
- 空闲状态:SCL和SDA均为高电平
- 起始信号(START):SCL高电平时,SDA从高变低
- 停止信号(STOP):SCL高电平时,SDA从低变高
- 数据位:SCL低电平时,准备数据;SCL高电平时,采样数据
- 应答信号(ACK):接收方在第9个时钟周期将SDA拉低表示接收成功
通信流程
- 主设备发送起始信号(START)
- 发送从设备地址(7位)和读/写位(1位)
- 从设备发送应答(ACK)
- 数据传输(8位一组),每组后跟应答位
- 主设备发送停止信号(STOP)
I2C位操作实现
首先需要实现基本的I2C底层函数:
GPIO配置
// 配置GPIO为开漏输出模式
void I2C_GPIO_Config(void) {// 使能GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // SCL: PB6, SDA: PB7GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);// 空闲状态,均为高电平GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);
}
I2C基本操作函数
// SCL和SDA控制函数
#define SCL_H GPIO_SetBits(GPIOB, GPIO_Pin_6)
#define SCL_L GPIO_ResetBits(GPIOB, GPIO_Pin_6)
#define SDA_H GPIO_SetBits(GPIOB, GPIO_Pin_7)
#define SDA_L GPIO_ResetBits(GPIOB, GPIO_Pin_7)
#define SDA_READ GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7)// 延迟函数
void I2C_Delay(void) {uint8_t i = 10; // 可根据时钟频率调整while(i--);
}// 起始信号
void I2C_Start(void) {SDA_H;SCL_H;I2C_Delay();SDA_L; // SDA从高到低,产生起始信号I2C_Delay();SCL_L; // 钳住I2C总线,准备发送或接收数据
}// 停止信号
void I2C_Stop(void) {SDA_L;SCL_H;I2C_Delay();SDA_H; // SDA从低到高,产生停止信号I2C_Delay();
}// 等待应答
uint8_t I2C_WaitAck(void) {uint8_t ack;SDA_H; // 释放SDAI2C_Delay();SCL_H; // 产生时钟脉冲I2C_Delay();ack = SDA_READ; // 读取SDA状态SCL_L;return ack; // 返回0表示有应答
}// 发送应答
void I2C_Ack(void) {SDA_L; // SDA拉低,表示ACKI2C_Delay();SCL_H;I2C_Delay();SCL_L;SDA_H; // 释放SDA
}// 发送非应答
void I2C_NAck(void) {SDA_H; // SDA保持高电平,表示NACKI2C_Delay();SCL_H;I2C_Delay();SCL_L;
}// 发送一个字节
void I2C_SendByte(uint8_t byte) {uint8_t i = 8;while(i--) {SCL_L;I2C_Delay();if(byte & 0x80)SDA_H;elseSDA_L;byte <<= 1;I2C_Delay();SCL_H;I2C_Delay();}SCL_L;
}// 读取一个字节
uint8_t I2C_ReadByte(uint8_t ack) {uint8_t i = 8;uint8_t byte = 0;SDA_H; // 释放SDA,准备读取数据while(i--) {byte <<= 1;SCL_L;I2C_Delay();SCL_H;I2C_Delay();if(SDA_READ)byte |= 0x01;}SCL_L;if(ack)I2C_Ack(); // 发送应答elseI2C_NAck(); // 发送非应答return byte;
}
I2C驱动代码编写
基于上面的底层函数,实现设备读写操作:
// 写入一个字节到指定设备的指定寄存器
uint8_t I2C_WriteReg(uint8_t DevAddr, uint8_t RegAddr, uint8_t data) {I2C_Start();I2C_SendByte(DevAddr << 1); // 设备地址 + 写位(0)if(I2C_WaitAck()) {I2C_Stop();return 1; // 无应答,失败}I2C_SendByte(RegAddr); // 寄存器地址if(I2C_WaitAck()) {I2C_Stop();return 1;}I2C_SendByte(data); // 写入数据if(I2C_WaitAck()) {I2C_Stop();return 1;}I2C_Stop();return 0; // 成功
}// 从指定设备的指定寄存器读取一个字节
uint8_t I2C_ReadReg(uint8_t DevAddr, uint8_t RegAddr) {uint8_t data;I2C_Start();I2C_SendByte(DevAddr << 1); // 设备地址 + 写位(0)if(I2C_WaitAck()) {I2C_Stop();return 0xFF; // 无应答,失败}I2C_SendByte(RegAddr); // 寄存器地址if(I2C_WaitAck()) {I2C_Stop();return 0xFF;}I2C_Start(); // 重复起始I2C_SendByte((DevAddr << 1) | 0x01); // 设备地址 + 读位(1)if(I2C_WaitAck()) {I2C_Stop();return 0xFF;}data = I2C_ReadByte(0); // 读取数据,发送非应答I2C_Stop();return data;
}
实际应用示例:MPU6050读取数据
#define MPU6050_ADDR 0x68 // MPU6050设备地址void MPU6050_Init() {I2C_WriteReg(MPU6050_ADDR, 0x6B, 0x00); // 唤醒MPU6050I2C_WriteReg(MPU6050_ADDR, 0x19, 0x07); // 采样率设置I2C_WriteReg(MPU6050_ADDR, 0x1A, 0x06); // 配置数字低通滤波器I2C_WriteReg(MPU6050_ADDR, 0x1B, 0x18); // 陀螺仪量程:±2000dpsI2C_WriteReg(MPU6050_ADDR, 0x1C, 0x01); // 加速度计量程:±2g
}void MPU6050_GetAcceleration(int16_t *ax, int16_t *ay, int16_t *az) {uint8_t buf[6];// 读取加速度计数据buf[0] = I2C_ReadReg(MPU6050_ADDR, 0x3B);buf[1] = I2C_ReadReg(MPU6050_ADDR, 0x3C);buf[2] = I2C_ReadReg(MPU6050_ADDR, 0x3D);buf[3] = I2C_ReadReg(MPU6050_ADDR, 0x3E);buf[4] = I2C_ReadReg(MPU6050_ADDR, 0x3F);buf[5] = I2C_ReadReg(MPU6050_ADDR, 0x40);*ax = (buf[0] << 8) | buf[1];*ay = (buf[2] << 8) | buf[3];*az = (buf[4] << 8) | buf[5];
}
SPI协议原理
SPI(Serial Peripheral Interface)是一种同步串行通信接口,使用四根线:
基本特性
- MOSI (Master Out Slave In):主设备发送,从设备接收
- MISO (Master In Slave Out):主设备接收,从设备发送
- SCK (Serial Clock):时钟信号,由主设备产生
- SS/CS (Slave Select/Chip Select):片选信号,用于选择从设备
工作模式
SPI有四种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)决定:
- 模式0:CPOL=0, CPHA=0,空闲时SCK低电平,第一个边沿采样
- 模式1:CPOL=0, CPHA=1,空闲时SCK低电平,第二个边沿采样
- 模式2:CPOL=1, CPHA=0,空闲时SCK高电平,第一个边沿采样
- 模式3:CPOL=1, CPHA=1,空闲时SCK高电平,第二个边沿采样
通信流程
- 主设备将对应从设备的CS线拉低(激活)
- 主设备通过SCK产生时钟信号
- 数据通过MOSI和MISO线同时双向传输
- 传输完成后,主设备将CS线拉高(释放)
SPI位操作实现
GPIO配置
// 配置SPI GPIO
void SPI_GPIO_Config(void) {// 使能GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;// 配置SCK、MOSI为推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; // SCK: PA5, MOSI: PA7GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置MISO为浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // MISO: PA6GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置CS为推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // CS: PA4GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// 初始状态:CS高,SCK低GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS高电平,不选中从设备GPIO_ResetBits(GPIOA, GPIO_Pin_5); // SCK低电平,模式0初始状态
}
SPI基本操作函数
// SPI引脚定义
#define SPI_CS_H GPIO_SetBits(GPIOA, GPIO_Pin_4)
#define SPI_CS_L GPIO_ResetBits(GPIOA, GPIO_Pin_4)
#define SPI_SCK_H GPIO_SetBits(GPIOA, GPIO_Pin_5)
#define SPI_SCK_L GPIO_ResetBits(GPIOA, GPIO_Pin_5)
#define SPI_MOSI_H GPIO_SetBits(GPIOA, GPIO_Pin_7)
#define SPI_MOSI_L GPIO_ResetBits(GPIOA, GPIO_Pin_7)
#define SPI_MISO GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)// 延迟函数
void SPI_Delay(void) {uint8_t i = 2;while(i--);
}// SPI发送并接收一个字节(模式0)
uint8_t SPI_ReadWriteByte(uint8_t data) {uint8_t i;uint8_t temp = 0;for(i = 0; i < 8; i++) {// 准备发送数据if(data & 0x80)SPI_MOSI_H;elseSPI_MOSI_L;data <<= 1; // 左移一位,准备下一位SPI_Delay();SPI_SCK_H; // 时钟上升沿,从设备采样MOSISPI_Delay();temp <<= 1; // 左移一位,为接收新的数据位腾出空间if(SPI_MISO)temp++; // 如果MISO为高,则置1SPI_SCK_L; // 时钟下降沿,主设备采样MISOSPI_Delay();}return temp; // 返回接收到的数据
}
SPI驱动代码编写
基于上面的底层函数,实现设备读写操作:
// 向指定寄存器写入一个字节
void SPI_WriteReg(uint8_t reg, uint8_t value) {SPI_CS_L; // 使能片选SPI_ReadWriteByte(reg); // 发送寄存器地址SPI_ReadWriteByte(value); // 发送数据SPI_CS_H; // 禁用片选
}// 从指定寄存器读取一个字节
uint8_t SPI_ReadReg(uint8_t reg) {uint8_t value;SPI_CS_L; // 使能片选SPI_ReadWriteByte(reg | 0x80); // 发送寄存器地址(最高位置1表示读操作)value = SPI_ReadWriteByte(0xFF); // 发送任意值,读取结果SPI_CS_H; // 禁用片选return value;
}// 从指定寄存器读取多个字节
void SPI_ReadMulti(uint8_t reg, uint8_t *buf, uint8_t len) {SPI_CS_L; // 使能片选SPI_ReadWriteByte(reg | 0x80); // 发送寄存器地址(最高位置1表示读操作)while(len--) {*buf = SPI_ReadWriteByte(0xFF);buf++;}SPI_CS_H; // 禁用片选
}
实际应用示例:读取W25Q64闪存
// W25Q64命令定义
#define W25Q64_READ_ID 0x90
#define W25Q64_READ_DATA 0x03
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_ERASE_SECTOR 0x20
#define W25Q64_READ_STATUS 0x05// 读取W25Q64芯片ID
uint16_t W25Q64_ReadID(void) {uint16_t id = 0;SPI_CS_L;SPI_ReadWriteByte(W25Q64_READ_ID); // 发送读取ID命令SPI_ReadWriteByte(0x00); // 发送3个虚拟地址SPI_ReadWriteByte(0x00);SPI_ReadWriteByte(0x00);id |= SPI_ReadWriteByte(0xFF) << 8; // 读取厂商IDid |= SPI_ReadWriteByte(0xFF); // 读取设备IDSPI_CS_H;return id;
}// 读取W25Q64状态寄存器
uint8_t W25Q64_ReadStatus(void) {uint8_t status;SPI_CS_L;SPI_ReadWriteByte(W25Q64_READ_STATUS);status = SPI_ReadWriteByte(0xFF);SPI_CS_H;return status;
}// 等待W25Q64操作完成
void W25Q64_WaitBusy(void) {while((W25Q64_ReadStatus() & 0x01) == 0x01);
}// 读取W25Q64数据
void W25Q64_ReadData(uint32_t addr, uint8_t *buf, uint16_t len) {SPI_CS_L;SPI_ReadWriteByte(W25Q64_READ_DATA); // 发送读取命令SPI_ReadWriteByte((addr >> 16) & 0xFF); // 发送地址SPI_ReadWriteByte((addr >> 8) & 0xFF);SPI_ReadWriteByte(addr & 0xFF);while(len--) {*buf = SPI_ReadWriteByte(0xFF);buf++;}SPI_CS_H;
}
总结
I2C协议实现要点
- 使用开漏输出模式配置GPIO
- 实现起始、停止、发送、接收、应答等基本信号操作
- 按照协议时序编写读写函数
- 注意时钟速率控制和时序延迟
SPI协议实现要点
- 配置MOSI、SCK为输出,MISO为输入
- 确定使用的SPI模式(时钟极性和相位)
- 实现基本的读写字节函数
- 根据具体设备实现寄存器读写操作
注意事项
- 时序要严格遵循协议规范
- 延时函数需根据实际系统时钟频率调整
- 注意不同设备可能有特殊的地址或命令要求
- 调试时可以使用示波器观察信号波形
- 加入错误处理和超时机制提高鲁棒性
通过以上步骤,您可以实现对I2C和SPI协议的"手撕",即从底层位操作实现完整的通信协议,而不依赖于硬件外设。这种方式虽然占用CPU资源较多,但灵活性高,适用于不需要高速通信的场景或硬件外设不足的情况。
相关文章:
手撕I2C和SPI协议实现
手撕I2C和SPI协议实现 目录 I2C协议原理I2C位操作实现I2C驱动代码编写SPI协议原理SPI位操作实现SPI驱动代码编写 I2C协议原理 I2C(Inter-Integrated Circuit)是一种串行通信总线,使用两根线:SCL(时钟线)…...

人工智能+:职业价值的重构与技能升级
当“人工智能”成为产业升级的标配时,一个令人振奋的就业图景正在展开——不是简单的岗位替代,而是职业价值的重新定义。这场变革的核心在于,AI并非抢走工作机会,而是创造了人类与技术协作的全新工作范式。理解这一范式转换的逻辑…...

JVM部分内容
1.JVM内存区域划分 为什么要划分内存区域,JAVA虚拟机是仿照真实的操作系统进行设计的,JVM也就仿照了它的情况,进行了区域划分的设计。 JAVA进程也就是JAVA虚拟机会从操作系统申请内存空间给进程使用,JVM内存空间划分,…...
paddlehub搭建ocr服务
搭建环境: Ubuntu20.041080Ti显卡 由于GPU硬件比较老,是Pascal架构,只能支持到paddle2.4.2版本,更高版本无法支持;同时,因为paddle老版本的依赖发生了变化,有些地方存在冲突,花费了…...

python-leetcode 68.有效的括号
题目: 给定一个只包括“(”),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足:左括号必须用相同类型的右括号闭合;左括号必须以正确的顺序闭合,…...
人性的裂痕:社会工程学如何成为网络安全的隐形战场
引言 在技术高度发达的今天,网络安全防护墙看似坚不可摧,但黑客却总能找到一条“捷径”——利用人性的弱点。这种被称为“社会工程学”的攻击手段,不依赖复杂的代码漏洞,而是通过心理操纵和信息欺骗,让受害者主动交出…...
ObservableCollection序列化,和监听链表内元素变化
1.ObservableCollection序列化 情景:定义了A类、B类; A类里面有ObservableCollection<B>类型的属性,假设这个属性名称为BList; ObservableCollection<MotionIntervalSegmentation> motionIntervalSegmentation; [B…...

NLP学习路线图(四):Python编程语言
引言 自然语言处理(Natural Language Processing, NLP)是人工智能领域最引人注目的分支之一。从智能客服到机器翻译,从舆情分析到聊天机器人,NLP技术正在重塑人机交互的边界。本文将结合Python编程语言,带您走进NLP的…...
matlab实现无线通信组
无线通信组网涉及多个节点之间的通信,通常需要考虑节点的布局、信号传输、路径损耗、干扰等问题。在MATLAB中,可以通过模拟节点的位置、信号强度、路径损耗等因素来实现一个简单的无线通信组网程序。 1. 节点布局 首先,我们需要定义网络中的…...
基于单片机的室内采光及可燃气体泄漏报警装置设计
标题:基于单片机的室内采光及可燃气体泄漏报警装置设计 内容:1.摘要 随着人们对室内环境安全和舒适度要求的提高,设计一种能实时监测室内采光和可燃气体泄漏情况并及时报警的装置具有重要意义。本设计基于单片机实现室内采光及可燃气体泄漏报警功能,采用…...

Serverless爬虫架构揭秘:动态IP、冷启动与成本优化
一、问题背景:旧技术的瓶颈 在传统爬虫架构中,我们通常部署任务在本地机器或虚拟机中,搭配定时器调度任务。虽然这种方式简单,但存在以下明显缺陷: 固定IP易被封禁:目标网站如拼多多会通过IP频率监控限制…...

从单体到分布式:深入解析Data Mesh架构及其应用场景与价值
Data Mesh(数据网格)是一种新兴的数据架构范式,旨在解决传统集中式数据平台的可扩展性、敏捷性和治理问题。它强调领域驱动的分布式数据所有权、自助数据平台以及跨组织的协作,使数据成为产品,并通过去中心化的方式提高…...

AI大模型ms-swift框架实战指南(十三):Agent智能体能力构建指南
系列篇章💥 No.文章1AI大模型ms-swift框架实战指南(一):框架基础篇之全景概览2AI大模型ms-swift框架实战指南(二):开发入门之环境准备3AI大模型ms-swift框架实战指南(三)…...

LLM最后怎么输出值 解码语言模型:从权重到概率的奥秘
LM Head Weights(语言模型头部权重):左侧的“LM Head Weights”表示语言模型头部的权重矩阵,它是模型参数的一部分。权重矩阵与输入数据进行运算。Logits(未归一化对数概率):经过与LM Head Weig…...

Leetcode百题斩-回溯
回溯是一个特别经典的问题,也被排在了百题斩的第一部分,那么我们接下来来过一下这个系列。 这个系列一共八道题,偶然间发现我两年前还刷到这个系列的题,回忆起来当时刚经历淘系大变动与jf出走海外事件,大量同事离职闹…...

超小多模态视觉语言模型MiniMind-V 训练
简述 MiniMind-V 是一个超适合初学者的项目,让你用普通电脑就能训一个能看图说话的 AI。训练过程就像教小孩:先准备好图文材料(数据集),教它基础知识(预训练),再教具体技能…...

边缘云的定义、实现与典型应用场景!与传统云计算的区别!
一、什么是边缘云? 边缘云是一种分布式云计算架构,将计算、存储和网络资源部署在靠近数据源或终端用户的网络边缘侧(如基站、本地数据中心或终端设备附近),而非传统的集中式云端数据中心。 核心特征&…...
HarmonyOS 鸿蒙应用开发基础:父组件和子组件的通信方法总结
在鸿蒙开发中,ArkUI声明式UI框架提供了一种现代化、直观的方式来构建用户界面。然而,由于其声明式的特性,父组件与子组件之间的通信方式与传统的命令式框架有所不同。本文旨在详细探讨在ArkUI框架中,父组件和子组件通信的方法总结…...
小白的进阶之路系列之三----人工智能从初步到精通pytorch计算机视觉详解下
我们将继续计算机视觉内容的讲解。 我们已经知道了计算机视觉,用在什么地方,如何用Pytorch来处理数据,设定一些基础的设置以及模型。下面,我们将要解释剩下的部分,包括以下内容: 主题内容Model 1 :加入非线性实验是机器学习的很大一部分,让我们尝试通过添加非线性层来…...

Scrapy爬取heima论坛所有页面内容并保存到MySQL数据库中
前期准备: Scrapy入门_win10安装scrapy-CSDN博客 新建 Scrapy项目 scrapy startproject mySpider # 项目名为mySpider 进入到spiders目录 cd mySpider/mySpider/spiders 创建爬虫 scrapy genspider heima bbs.itheima.com # 爬虫名为heima ,爬…...
HarmonyOS NEXT~鸿蒙系统下的Cordova框架应用开发指南
HarmonyOS NEXT~鸿蒙系统下的Cordova框架应用开发指南 1. 简介 Apache Cordova是一个流行的开源移动应用开发框架,它允许开发者使用HTML5、CSS3和JavaScript构建跨平台移动应用。随着华为鸿蒙操作系统(HarmonyOS)的崛起,将Cordova应用适配到…...

com.alibaba.fastjson2 和com.alibaba.fastjson 区别
1,背景 最近发生了一件很奇怪的事:我们的服务向第三方发送请求参数时,第三方接收到的字段是首字母大写的 AppDtoList,但我们需要的是小写的 appDtoList。这套代码是从其他项目A原封不动复制过来的,我们仔细核对了项目…...
探索数据结构的时间与空间复杂度:编程世界的效率密码
在计算机科学的世界里,数据结构是构建高效算法的基石。而理解数据结构的时间复杂度和空间复杂度,则是评估算法效率的关键。无论是优化现有代码,还是设计新的系统,复杂度分析都是程序员必须掌握的核心技能。本文将深入探讨这两个重…...
std::ranges::views::stride 和 std::ranges::stride_view
std::ranges::views::stride 是 C23 中引入的一个范围适配器,用于创建一个视图,该视图只包含原始范围中每隔 N 个元素的元素(即步长为 N 的元素)。 基本概念 std::ranges::stride_view 是一个范围适配器,接受一个输…...

了解Android studio 初学者零基础推荐(2)
在kotlin中编写条件语句 if条件语句 fun main() {val trafficLight "gray"if (trafficLight "red") {println("Stop!")} else if (trafficLight "green") {println("go!")} else if (trafficLight "yellow")…...
矩阵短剧系统:如何用1个后台管理100+小程序?技术解析与实战应用
引言:短剧行业的效率革命 2025年,短剧市场规模已突破千亿,但传统多平台运营模式面临重复开发成本高、用户数据分散、内容同步效率低等痛点。行业亟需一种既能降本增效又能聚合流量的解决方案——“矩阵短剧系统”。通过“1个后台管理100小程…...

C# 初学者的 3 种重构模式
(Martin Fowlers Example) 1. 积极使用 Guard Clause(保护语句) "如果条件不满足,立即返回。将核心逻辑放在最少缩进的地方。" 概念定义 Guard Clause(保护语句) 是一种在函数开头检查特定条件是否满足&a…...

MySQL 数据类型深度全栈实战,天花板玩法层出不穷!
在 MySQL 数据库的世界里,数据类型是构建高效、可靠数据库的基石。选择合适的数据类型,不仅能节省存储空间,还能提升数据查询和处理的性能 目录 编辑 一、MySQL 数据类型总览 二、数值类型 三、字符串类型 四、日期时间类型 五、其他…...

前端vscode学习
1.安装python 打开Python官网:Welcome to Python.org 一定要点PATH,要不然要自己设 点击install now,就自动安装了 键盘winR 输入cmd 点击确定 输入python,回车 显示这样就是安装成功了 2.安装vscode 2.1下载软件 2.2安装中文 2.2.1当安…...
自动驾驶传感器数据处理:Python 如何让无人车更智能?
自动驾驶传感器数据处理:Python 如何让无人车更智能? 1. 引言:为什么自动驾驶离不开数据处理? 自动驾驶一直被誉为人工智能最具挑战性的应用之一,而其背后的核心技术正是 多传感器融合与数据处理。 一辆智能驾驶汽车,通常搭载: 激光雷达(LiDAR) —— 3D 环境感知,…...