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); // 等待大约40us
 	EE_IIC_SDA(0); // DATA引脚输出低
	EE_IIC_Delay(4); // 等待大约40us
	EE_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); // 等待大约40us
	EE_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); // 等待约10us
	while (EE_READ_SDA()) // 一直读,直到读取到低电平应答
	{
		ucErrTime++;
		if (ucErrTime > 250)
		{
			EE_IIC_Stop();
			return 1;
		}
	}
	EE_IIC_SCL(0); // 时钟输出0
	return 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(); // 发送ACK
	else
		EE_IIC_NAck(); // 发送nACK
	return 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代码
  // 测试写入和读取EEPROM
	EE_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的频率在闪烁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/667316.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【STL源码剖析-空间配置器】stack、queue简单实现

举头天外望 无我这般人 目录 stack 的概述 stack 的实现 queue 的概述 queue 的实现 契子✨ 我们之前学过了 vector、list 这些 STL 的&#xff08;容器&#xff09; 而我们今天将要学习空间配置器 -- stack、queue&#xff0c;那什么是空间配置器呢&#xff1f; 简单来讲就是…

【百度之星比赛】

新材料 直接模拟&#xff1a;因为要考虑上次出现的位置&#xff0c;所以使用map映射最好&#xff0c;如果没有出现过就建立新映射&#xff0c;如果出现过但是已经反应过就跳过&#xff0c;如果出现过但是不足以反应&#xff0c;就建立新映射&#xff0c;如果能反应就反应&#…

【WEEK14】 【DAY4】Swagger第二部分【中文版】

2024.5.30 Thursday 接上文【WEEK14】 【DAY3】Swagger第一部分【中文版】 目录 16.4.配置扫描接口16.4.1.修改SwaggerConfig.java16.4.1.1.使用.basePackage()方法指定扫描的包路径16.4.1.2.其他扫描方式均可在RequestHandlerSelectors.class中查看源码 16.4.2.仍然是修改Swag…

HttpSecurity 是如何组装过滤器链的

有小伙伴们问到这个问题&#xff0c;简单写篇文章和大伙聊一下。 一 SecurityFilterChain 首先大伙都知道&#xff0c;Spring Security 里边的一堆功能都是通过 Filter 来实现的&#xff0c;无论是认证、RememberMe Login、会话管理、CSRF 处理等等&#xff0c;各种功能都是通…

solr-8.11.3

https://solr.apache.org/downloads.html https://archive.apache.org/dist/solr/solr/ F:\Document_Solr.apache.org\solr-8.11.3\bin Microsoft Windows [版本 10.0.19045.2965] (c) Microsoft Corporation。保留所有权利。 C:\Users\Administrator>F: F:\> F:\>…

为啥装了erlang,还报错erl: command not found?

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 问题背景&#xff1a; 在一台不通外网的服务器上装rabbitmq&#xff0c;然后在启动的时候&#xff0c;遇到了报错 “/usr/lib/…

linux可观测性ebpf(一) ----------- 环境搭建

参考书籍 开发环境 Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-150-generic x86_64) 1.1 下载内核源码 cd /usr/src/ sudo git clone -b v5.4 https://github.com/torvalds/linux.git1.2 下载书中代码 git clone https://github.com/bpftools/linux-observability-with-bpf1.3 编…

LeetCode2300咒语和药水的成功对数

题目描述 解析 先对药水排序后每个咒语去二分查找最低满足的药水的位置。 class Solution {public int[] successfulPairs(int[] spells, int[] potions, long success) {int n spells.length, m potions.length;Arrays.sort(potions);for (int i 0; i < n; i) {long ta…

亚信安慧AntDB数据库与华为数据存储完成兼容性互认证

迎接数智时代&#xff0c;供给核心科技。日前&#xff0c;湖南亚信安慧科技有限公司&#xff08;简称&#xff1a;亚信安慧&#xff09;与华为技术有限公司&#xff08;简称&#xff1a;华为&#xff09;&#xff0c;完成了AntDB数据库产品与OceanProtect备份一体机及Oceanstor…

PHP框架开发的内容付费问答解惑系统附带seo优化

default默认是百度问答模板 sowenda是高仿360问答的。 soso模板是仿腾讯soso问答界面。 一套wap模板&#xff0c;仿天涯问答的手机版。 pc和wap模板后台设置里自由切换&#xff0c;还可以绑定手机独立二级域名。 强大的搜索功能&#xff0c;支持xunsearch全文检索&#xff0c;s…

代码随想录——二叉搜索树的最小绝对差(Leetcode530)

题目链接 层序遍历 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) …

Docker中布置Jenkins实现Android项目的自动化构建

因项目需要&#xff0c;要在服务器上使用Jenkins完成Android项目的自动化构建&#xff0c;但服务器上登录的账户没有管理员权限&#xff0c;无法用sudo命令&#xff0c;因此需要把相应环境布置在docker中。 环境搭建 docker容器相关命令 创建容器 docker create -it contai…

跨境物流系统选择标准:能充分试用,合作灵活的才是好系统

对国际物流商而言&#xff0c;大家都知道跨境物流系统对业务优化有多重要。但是想选择一套适合自己的跨境物流系统却并不是一件简单的事情。 最主要的原因就是现在市场上的国际物流系统确实太多了。不同的功能设计&#xff0c;定价设计&#xff0c;让物流商非常头疼&#xff0…

基于Lumerical fdtd进行无序光子晶体波导的仿真设计及优化

光子晶体是一类通过不同折射率介质周期性的排列而形成的具有光波长量级的周期性人工微型结构&#xff0c;相比于传统晶体来说&#xff0c;由于介电函数的周期性分布&#xff0c;光子晶体也会产生一些类似于传统晶体的带隙&#xff0c;使光局域在带隙中无法传播。我们在完整的光…

JavaScript解构赋值

一、数组解构 以上要么不好记忆&#xff0c;要么书写麻烦&#xff0c;此时可以使用解构赋值的方法让代码更简洁。 数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。 基本语法&#xff1a; 1、赋值运算符左侧的[]用于批量声明变量&#xff0c;右侧数组的单元值将…

轻量级动态可监控线程池 - DynamicTp

一、背景介绍 使用线程池ThreadPoolExecutor的过程中你是否有以下痛点呢&#xff1f; 代码中创建了一个 ThreadPoolExecutor&#xff0c;但是不知道那几个核心参数设置多少比较合适凭经验设置参数值&#xff0c;上线后发现需要调整&#xff0c;改代码重新发布服务&#xff0c…

关于ida如何进行远程linux调试(详解)

首先我们需要安装工具软件VMware虚拟机和finalshell&#xff0c;并在虚拟机中安装centos 7系统&#xff0c;还要将finalshell连接到该系统中&#xff0c;具体操作可以去b站搜黑马Linux学习&#xff0c;学完该课程的p5&#xff0c;p6&#xff0c;p8即可&#xff0c;我接下来讲的…

api网关kong对高频的慢接口进行熔断

一、背景 在生产环境&#xff0c;后端服务的接口响应非常慢&#xff0c;是因为数据库未创建索引导致。 如果QPS低的时候&#xff0c;因为后端服务有6个高配置的节点&#xff0c;虽然接口慢&#xff0c;还未影响到服务的正常运行。 但是&#xff0c;当QPS很高的时候&#xff0c…

顶级手机数据恢复软件 [2024 更新]

什么是最好的手机数据恢复软件&#xff1f;在这篇文章中&#xff0c;您将免费了解 6 款最佳手机数据恢复软件&#xff0c;并了解有关如何恢复数据的完整指南。 什么是最好的手机数据恢复软件&#xff1f; 手机数据恢复软件是从智能手机中检索丢失或删除的文件&#xff0c;消息…

(自适应手机端)响应式服装服饰外贸企业网站模板

(自适应手机端)响应式服装服饰外贸企业网站模板PbootCMS内核开发的网站模板&#xff0c;该模板适用于服装服饰网站、外贸网站等企业&#xff0c;当然其他行业也可以做&#xff0c;只需要把文字图片换成其他行业的即可&#xff1b;自适应手机端&#xff0c;同一个后台&#xff0…