本专栏记录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的频率在闪烁。