STM32G030C8T6:EEPROM读写实验(I2C通信)--M24C64
本专栏记录STM32开发各个功能的详细过程,方便自己后续查看,当然也供正在入门STM32单片机的兄弟们参考;
本小节的目标是,系统主频64 MHZ,采用高速外部晶振,实现PB11,PB10 引脚模拟I2C 时序,对M24C08 的EEPROM 进行读。
原理:通过模拟I2C接(PB10:CLK,PB11:DTA)与M24C08 EEPROM进行读写实验。
涉及到的知识:配置I2C通信,STM32CubeMX的使用
文章目录
- 1 新建工程
- 2 配置SWD下载引脚
- 3 配置RCC
- 4 设置系统主频
- 5 生成工程
- 6 增加代码实现PB10,PB11 模拟I2C 时序,从而实现EEPROM数据读写
1 新建工程
点击File 菜单下的New Project

选择芯片型号,如下图所示先输入芯片型号,目前这边输入STM32G030C8,

双击选择,就确定了芯片型号,界面会变成如下图所示

2 配置SWD下载引脚
如下图所示,在Pinout&Configuration 栏目的System Core 下,先点击SYS,再勾选Serial Wire 框,
配置好SWD 下载引脚设置:

3 配置RCC
如下图,先点击RCC,在HSE 配置中选择Crystal/Ceramic Resonator 外部晶振设

4 设置系统主频
如下图, 先点击Clock Configuration 栏目,按下图的1,2,3,4 步骤完成系统64MHZ 主频设置:

5 生成工程
按照下图的步骤,进行项目配置,项目名称和路径设置等,生成项目的类型选择STM32CubeIDE(我这里以STM32CubeIDE为例,如果你要试用keil5,那就选择MDK-RAM,如果要使用makefile,就选择Makefile),注意项目名称和路径不要有中文名;

最后全部设置完毕后点击create code,生成项目代码:

生成的工程如下图所示:

6 增加代码实现PB10,PB11 模拟I2C 时序,从而实现EEPROM数据读写
如下图所示,在Core/Src下面增加24C64.c 文件,里面是用I/O 口模拟I2C 总线实现EEPROM读写驱动。

24C64.h:
#ifndef M24C64_H
#define M24C64_H#include "main.h"#define EE_ADDR 0xA0 // EEPROM地址,地址管脚全接地,为0xA0
#define EE_SCL_PIN GPIO_PIN_10 //模拟IIC的SCL信号
#define EE_SDA_PIN GPIO_PIN_11 //模拟IIC的SDA信号
#define EE_IIC_SCL(val) HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10,val) //SCL 输出高或者低
#define EE_IIC_SDA(val) HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11,val) //SDA 输出高或者低void EE_SDA_IN(void); // PB11配置成输入
void EE_SDA_OUT(void); // PB11配置成开漏输出
void EE_SCK_OUT(void); // PB10配置成开漏输出
unsigned char EE_READ_SDA(void); // 读DATA引脚状态void EE_IIC_Delay(uint16_t us); // IIC延时
void EE_IIC_Init(void); // IIC初始化
void EE_IIC_Start(void); // 开始
void EE_IIC_Stop(void); // 停止
uint8_t EE_IIC_WaitAck(void); // 等待应答
void EE_IIC_Ack(void); // 发送应答
void EE_IIC_NAck(void); // 发送非应答
void EE_IIC_SendByte(uint8_t data); // 发送一个字节
uint8_t EE_IIC_ReadByte(uint8_t ack); // 读取1字节// M24C64特定函数
uint8_t EE_IIC_ReadByteFromSlave(uint8_t I2C_Addr, uint16_t reg, uint8_t *buf);
uint8_t EE_IIC_SendByteToSlave(uint8_t I2C_Addr, uint16_t reg, uint8_t data);#endif // M24C64_H/*********************************************************************************************************END FILE
*********************************************************************************************************/
24C08.c
#include "24C64.h"void EE_SDA_IN(void) // PB11配置成输入
{__HAL_RCC_GPIOB_CLK_ENABLE(); // GPIO时钟使能GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_11;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}void EE_SDA_OUT(void) // PB11配置成开漏输出
{__HAL_RCC_GPIOB_CLK_ENABLE(); // GPIO时钟使能GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_11;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}void EE_SCK_OUT(void) // PB10配置成开漏输出
{__HAL_RCC_GPIOB_CLK_ENABLE(); // GPIO时钟使能GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}// 读DATA引脚状态
unsigned char EE_READ_SDA(void)
{return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11);
}// IIC延时
void EE_IIC_Delay(uint16_t us)
{uint16_t j;for (j = 0; j < us; j++){for (int i = 0; i < 20; i++){__asm("NOP"); // 等待1个指令周期,系统主频16M}}
}// IIC初始化
void EE_IIC_Init(void)
{EE_SCK_OUT(); // CLK引脚配置成输出EE_SDA_OUT(); // DATA引脚配置成输出EE_IIC_SCL(1); // CLK引脚输出高EE_IIC_SDA(1); // DATA引脚输出高
}// 开始
void EE_IIC_Start(void)
{EE_SDA_OUT(); // DATA引脚配置成输出EE_IIC_SDA(1); // DATA引脚输出高EE_IIC_SCL(1); // CLK引脚输出高EE_IIC_Delay(4); // 等待大约40usEE_IIC_SDA(0); // DATA引脚输出低EE_IIC_Delay(4); // 等待大约40usEE_IIC_SCL(0); // CLK引脚输出低,钳住I2C总线,准备发送或接收数据
}// 停止
void EE_IIC_Stop(void)
{EE_SDA_OUT(); // DATA引脚配置成输出EE_IIC_SCL(0); // CLK引脚输出低EE_IIC_SDA(0); // DATA引脚输出低EE_IIC_Delay(4); // 等待大约40usEE_IIC_SCL(1); // CLK引脚输出高EE_IIC_SDA(1); // DATA引脚输出高,发送I2C总线结束信号EE_IIC_Delay(4); // 等待大约40us
}// 等待应答
uint8_t EE_IIC_WaitAck(void)
{uint8_t ucErrTime = 0;EE_SDA_IN(); // DATA引脚配置成输入(从机给一个低电平做为应答)EE_IIC_SDA(1);EE_IIC_Delay(1);EE_IIC_SCL(1);EE_IIC_Delay(1); // 等待约10uswhile (EE_READ_SDA()) // 一直读,直到读取到低电平应答{ucErrTime++;if (ucErrTime > 250){EE_IIC_Stop();return 1;}}EE_IIC_SCL(0); // 时钟输出0return 0;
}// 发送应答
void EE_IIC_Ack(void)
{EE_IIC_SCL(0);EE_SDA_OUT();EE_IIC_SDA(0);EE_IIC_Delay(1);EE_IIC_SCL(1);EE_IIC_Delay(2);EE_IIC_SCL(0);
}// 发送非应答
void EE_IIC_NAck(void)
{EE_IIC_SCL(0);EE_SDA_OUT();EE_IIC_SDA(1);EE_IIC_Delay(1);EE_IIC_SCL(1);EE_IIC_Delay(1);EE_IIC_SCL(0);
}// 发送一个字节
void EE_IIC_SendByte(uint8_t data)
{uint8_t t;EE_SDA_OUT();EE_IIC_SCL(0); // 拉低时钟开始数据传输for (t = 0; t < 8; t++){EE_IIC_SDA((data & 0x80) >> 7); // 发送数据EE_IIC_Delay(1);EE_IIC_SCL(1);data <<= 1;EE_IIC_Delay(1);EE_IIC_SCL(0);}EE_IIC_Delay(1);
}// 读取1字节
uint8_t EE_IIC_ReadByte(uint8_t ack)
{uint8_t i, receive = 0;EE_SDA_IN(); // SDA设置为输入模式 等待接收从机返回数据for (i = 0; i < 8; i++){EE_IIC_SCL(0);EE_IIC_Delay(1);EE_IIC_SCL(1);receive <<= 1;if (EE_READ_SDA()) receive++; // 读取从机发送的电平,如果是高,就记录高EE_IIC_Delay(1);}if (ack)EE_IIC_Ack(); // 发送ACKelseEE_IIC_NAck(); // 发送nACKreturn receive;
}// 从EE指定地址读取一个字节
uint8_t EE_IIC_ReadByteFromSlave(uint8_t I2C_Addr, uint16_t reg, uint8_t *buf)
{EE_IIC_Start();EE_IIC_SendByte(I2C_Addr); // 发送从机地址if (EE_IIC_WaitAck()) // 如果从机未应答则数据发送失败{EE_IIC_Stop();return 1;}EE_IIC_SendByte((reg >> 8) & 0xFF); // 发送寄存器高位地址EE_IIC_WaitAck();EE_IIC_SendByte(reg & 0xFF); // 发送寄存器低位地址EE_IIC_WaitAck();EE_IIC_Start();EE_IIC_SendByte(I2C_Addr + 1); // 进入接收模式EE_IIC_WaitAck();*buf = EE_IIC_ReadByte(0);EE_IIC_Stop(); // 产生一个停止条件return 0;
}// 发送一个字节内容到EE指定地址
uint8_t EE_IIC_SendByteToSlave(uint8_t I2C_Addr, uint16_t reg, uint8_t data)
{EE_IIC_Start();EE_IIC_SendByte(I2C_Addr); // 发送从机地址if (EE_IIC_WaitAck()){EE_IIC_Stop();return 1; // 从机地址写入失败}EE_IIC_SendByte((reg >> 8) & 0xFF); // 发送寄存器高位地址EE_IIC_WaitAck();EE_IIC_SendByte(reg & 0xFF); // 发送寄存器低位地址EE_IIC_WaitAck();EE_IIC_SendByte(data);if (EE_IIC_WaitAck()){EE_IIC_Stop();return 1; // 数据写入失败}EE_IIC_Stop(); // 产生一个停止条件return 0;
}
然后如下图所示,24C08.c 文件,主要是把SCL 引脚改成PB10,SDA引脚改成PB11,还有EEPROM 驱动所需的基本I/O 操作函数,还有实现时序所需的等待函数。
然后打开main.c文件,按照下图操作添加代码:

extern void EE_IIC_Init(void);
extern uint8_t EE_EE_IIC_SendByteToSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t data);
extern uint8_t EE_IIC_ReadByteFromSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t *buf);
unsigned char EEDATA;//存放EEPROM读取出来的数据,1个字节

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);#if 1//M24C64代码// 测试写入和读取EEPROMEE_IIC_Init();uint16_t test_addr = 0x0000;uint8_t test_data = 0x55;uint8_t read_data = 0;// 写入测试数据EE_IIC_SendByteToSlave(EE_ADDR, test_addr, test_data);HAL_Delay(10); // 确保写入完成// 读取测试数据EE_IIC_ReadByteFromSlave(EE_ADDR, test_addr, &read_data);
#endif/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE */// 循环检测读取的数据if (read_data == test_data) {// 成功读取while(1){HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);HAL_Delay(50);}} else {// 读取失败while(1){HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);HAL_Delay(500);}}/* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}
然后编译,调试,查看EEDATA变量的值,结果如下图所示:

我们写进去的值时0x55,最后读出来的值是85'U',查一下ASCII码:

0x55对应的就是85'U',证明我们的结果是对的,证明此EEPROM读写实验成功。
此时单片机上PB9对应的小灯在以50hz的频率在闪烁。
相关文章:
STM32G030C8T6:EEPROM读写实验(I2C通信)--M24C64
本专栏记录STM32开发各个功能的详细过程,方便自己后续查看,当然也供正在入门STM32单片机的兄弟们参考; 本小节的目标是,系统主频64 MHZ,采用高速外部晶振,实现PB11,PB10 引脚模拟I2C 时序,对M24C08 的EEPRO…...
opencascade 布尔运算笔记
BRepAlgoAPI_Common 对两个topods求解 没有公共部分也返回结果了 我想要的结果是没有公共部分返回false 在 Open CASCADE 中使用 BRepAlgoAPI_Common 进行布尔操作时,即使两个 TopoDS_Shape 没有公共部分,操作仍会返回一个结果。为了判断两个形状是否确…...
GPT-4o:人工智能新纪元的突破与展望
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
标准化、信息化、数字化、智能化、智慧化与数智化
近年来,标准化、信息化、数字化、智能化、智慧化以及数智化等词汇频繁出现,并逐渐成为业界热议的焦点。在国内,以华为、BAT等为代表的领军企业,不断强调“数字化转型”的重要性,并致力于推动其深入实施。与此同时&…...
14-JavaScript中的点操作符与方括号操作符
JavaScript中的点操作符与方括号操作符:简单理解与应用 笔记分享 在JavaScript中,访问对象的属性有两种常见方式:点操作符(.)和方括号操作符([])。尽管它们在很多情况下可以互换使用࿰…...
智慧大屏是如何实现数据可视化的?
智慧大屏,作为数据可视化的重要载体,已在城市管理、交通监控、商业运营等领域广泛应用。本文旨在阐述智慧大屏实现数据可视化的关键技术和方法,包括数据源管理、数据处理、视觉编码、用户界面与交互设计等。 大屏通过接入企业内部的数据库系…...
【JVM精通之路】垃圾回收-三色标记算法
首先预期你已经基本了解垃圾回收的相关知识,包括新生代垃圾回收器,老年代垃圾回收器,以及他们的算法,可达性分析等等。 先想象一个场景 最开始黑色节点是GC-Roots的根节点,这些对象有这样的特点因此被选为垃圾回收的根…...
Redis缓存(笔记一:缓存介绍和数据库启动)
目录 1、NoSQL数据库简介 2、Redis介绍 3、Redis(win系统、linux系统中操作) 3.1 win版本Redis启动 3.2 linux版本Redis启动 1、NoSQL数据库简介 技术的分类:(发展史) 1、解决功能性的问题:Java、Jsp、RDBMS、Tomcat、HTML、…...
OrangePi Kunpeng Pro套装测评:开箱与基本功能测试
前言 大家好,我是起个网名真难。非常荣幸受到香橙派的邀请,同时也是第一次做这个事情,很荣幸对香橙派与华为鲲鹏在2024年5月12日联合发布的新品——香橙派Kunpeng Pro开发板进行深入的评测。这款开发板是香橙派与华为鲲鹏合作推出的高性能平…...
RocketMQ教程(二):RocketMQ以及控制台的安装
RocketMQ-Console RocketMQ-Console 是一个针对 Apache RocketMQ 的开源 Web 监控和管理平台。它提供了一个用户友好的界面,通过 Web 浏览器允许用户对 RocketMQ 集群进行管理和监控。这个控制台使得管理和监测 RocketMQ 集群变得更加直观和方便,特别是对于不熟悉命令行操作的…...
电脑记事本怎么恢复之前的内容记录
每个人都曾有过这样的时刻——在记事本上精心记录下的重要内容,一不小心就被删除了。那种心情,仿佛一瞬间从山顶跌落到谷底,无尽的懊悔涌上心头。我也曾遭遇过这样的困境,那些消失的文字对我来说意义非凡,它们的丢失仿…...
Windows下设置pip代理(proxy)
使用场景 正常网络情况下我们安装如果比较多的python包时,会选择使用这种 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-hostpypi.tuna.tsinghua.edu.cn 国内的镜像来加快下载速度。 但是,当这台被限制上…...
【调试笔记-20240530-Linux-在 OpenWRT-23.05 上为 nginx 配置 HTTPS 网站】
调试笔记-系列文章目录 调试笔记-20240530-Linux-在 OpenWRT-23.05 上为 nginx 配置 HTTPS 网站 文章目录 调试笔记-系列文章目录调试笔记-20240530-Linux-在 OpenWRT-23.05 上为 nginx 配置 HTTPS 网站 前言一、调试环境操作系统:OpenWrt 23.05.3调试环境调试目标…...
安装 hbase(伪分布式)
目录 1、安装 jdk8 (1)选择 jdk 版本 (2)下载 jdk 并解压 (3)配置环境变量 2、安装hadoop (1)添加 hadoop 用户,配置免密登录 (2)下载 hado…...
Angular-数组循环
简单数组 .ts-定义一个数组 // 第一种定义方式 public arr1:string[] [aa,bb,cc]; // 完整版 arr2:string[] [aa,bb,cc]; // 省略版 public objects:any[] [{username:Echoo,age:18},{username:Amily,age:39},{username:Mike,age:34}]; // 元素是对象 //第二种定…...
初级网络工程师之入门到入狱(一)
本文是我在学习过程中记录学习的点点滴滴,目的是为了学完之后巩固一下顺便也和大家分享一下,日后忘记了也可以方便快速的复习。 网络工程师从入门到入狱 前言一、交换机二、路由器三、DHCP(动态主机配置协议)四、路由器配置 DHCP自…...
数据挖掘与机器学习——分类算法
目录 机器学习算法最普通分类: 分类算法的定义: 分类算法的应用: 分类器实现分类: 分类器的构建标准: 概率模型: 贝叶斯公式: 朴素贝叶斯算法(朴素贝叶斯分类器)…...
变压器励磁涌流MATLAB仿真模型
微❤关注“电气仔推送”获得资料(专享优惠) 变压器励磁涌流的产生机理 1、变压器是电力系统的关键部分,在实际的 运行中,变压器需要进行相应的充电,而在充电的过 程中,就需要进行开合闸作业。在开合闸作业…...
ToxVidLLM:一个用于检测有害视频的多模态多任务框架
在一个社交媒体平台赋予用户成为内容创作者力量的时代,数字领域见证了前所未有的信息传播激增,到2023年,近82%的互联网流量是视频内容。因此,像抖音和YouTub这样的平台已经成为主要的信息来源。一个显著的统计数据凸显了这些平台的…...
比较(二)利用python绘制雷达图
比较(二)利用python绘制雷达图 雷达图(Radar Chart)简介 雷达图可以用来比较多个定量变量,也可以用于查看数据集中变量的得分高低,是显示性能表现的理想之选。缺点是变量过多容易造成阅读困难。 快速绘制…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
