当前位置:   article > 正文

2024年C C++最全STM32硬件I2C与软件模拟I2C超详解_stm32 i2c(1),论程序员成长的正确姿势_stm32 c++

stm32 c++

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

3.STM32随机读取EEPROM内部任何地址的数据

在这里插入图片描述
在这里插入图片描述

4.STM32随机顺序读取EEPROM内部任何地址的数据

在这里插入图片描述
EEPROM一共有256个字节对应的地址为(0~255)
当读取到最后一个字节,也就是255地址,第256个字节,在读取又会从头(第一个字节数据)开始读取。

六.硬件I2C读写EEPROM实验

实验目的

STM32作为主机向从机EEPROM存储器写入256个字节的数据
STM32作为主机向从机EEPROM存储器读取写入的256个字节的数据

读写成功亮绿灯,读写失败亮红灯

实验原理

  • 硬件设计
    原理图
    在这里插入图片描述
    实物图
    在这里插入图片描述

编程要点
(1) 配置通讯使用的目标引脚为开漏模式;
(2) 编写模拟 I2C 时序的控制函数;
(3) 编写基本 I2C 按字节收发的函数;
(4) 编写读写 EEPROM 存储内容的函数;
(5) 编写测试程序,对读写数据进行校验。

两个引脚PB6,PB7都要配置成复用的开漏输出
这里有一个注意的点,你配置成输出模式,并不会影响引脚的输入功能

详情请看——>GPIO端口的八种工作模式
在这里插入图片描述

源码

i2c_ee.h
前面理论已经讲得已经很详细了,直接上代码叭!!

#ifndef \_\_IIC\_EE\_H
#define \_\_IIC\_EE\_H

#include "stm32f10x.h"
#include <stdio.h>
//IIC1
#define EEPROM\_I2C I2C1
#define EEPROM\_I2C\_CLK RCC\_APB1Periph\_I2C1
#define EEPROM\_I2C\_APBxClkCmd RCC\_APB1PeriphClockCmd
#define EEPROM\_I2C\_BAUDRATE 400000

// IIC1 GPIO 引脚宏定义
#define EEPROM\_I2C\_SCL\_GPIO\_CLK (RCC\_APB2Periph\_GPIOB)
#define EEPROM\_I2C\_SDA\_GPIO\_CLK (RCC\_APB2Periph\_GPIOB)
#define EEPROM\_I2C\_GPIO\_APBxClkCmd RCC\_APB2PeriphClockCmd
     
#define EEPROM\_I2C\_SCL\_GPIO\_PORT GPIOB 
#define EEPROM\_I2C\_SCL\_GPIO\_PIN GPIO\_Pin\_6
#define EEPROM\_I2C\_SDA\_GPIO\_PORT GPIOB
#define EEPROM\_I2C\_SDA\_GPIO\_PIN GPIO\_Pin\_7

//STM32自身地址1 与从机设备地址不相同即可(7位地址)
#define STM32\_I2C\_OWN\_ADDR 0x6f
//EEPROM设备地址
#define EEPROM\_I2C\_Address 0XA0
#define I2C\_PageSize 8


//等待次数
#define I2CT\_FLAG\_TIMEOUT ((uint32\_t)0x1000)
#define I2CT\_LONG\_TIMEOUT ((uint32\_t)(10 \* I2CT\_FLAG\_TIMEOUT))



/\*信息输出\*/
#define EEPROM\_DEBUG\_ON 0
#define EEPROM\_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM\_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM\_DEBUG(fmt,arg...) do{\
 if(EEPROM\_DEBUG\_ON)\
 printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",\_\_LINE\_\_, ##arg);\
 }while(0)

void I2C\_EE\_Config(void);
void EEPROM\_Byte\_Write(uint8\_t addr,uint8\_t data);	
uint32\_t  EEPROM\_WaitForWriteEnd(void);	
uint32\_t  EEPROM\_Page\_Write(uint8\_t addr,uint8\_t \*data,uint16\_t Num_ByteToWrite);																					
uint32\_t  EEPROM\_Read(uint8\_t \*data,uint8\_t addr,uint16\_t Num_ByteToRead);
void I2C\_EE\_BufferWrite(uint8\_t\* pBuffer,uint8\_t WriteAddr, uint16\_t NumByteToWrite);
#endif /\* \_\_IIC\_EE\_H \*/


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

i2c_ee.c

#include "i2c\_ee.h"


//设置等待时间
static __IO uint32\_t  I2CTimeout = I2CT_LONG_TIMEOUT;   

//等待超时,打印错误信息
static uint32\_t I2C\_TIMEOUT\_UserCallback(uint8\_t errorCode);


void I2C\_EE\_Config(void)
{
	GPIO_InitTypeDef    GPIO_InitStuctrue;
	I2C_InitTypeDef     I2C_InitStuctrue;
	//开启GPIO外设时钟
	EEPROM\_I2C\_GPIO\_APBxClkCmd(EEPROM_I2C_SCL_GPIO_CLK|EEPROM_I2C_SDA_GPIO_CLK,ENABLE);
	//开启IIC外设时钟
	EEPROM\_I2C\_APBxClkCmd(EEPROM_I2C_CLK,ENABLE);
	
	//SCL引脚-复用开漏输出
  GPIO_InitStuctrue.GPIO_Mode=GPIO_Mode_AF_OD;
  GPIO_InitStuctrue.GPIO_Pin=EEPROM_I2C_SCL_GPIO_PIN;
	GPIO_InitStuctrue.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO\_Init(EEPROM_I2C_SCL_GPIO_PORT,&GPIO_InitStuctrue);
	//SDA引脚-复用开漏输出
	GPIO_InitStuctrue.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStuctrue.GPIO_Pin = EEPROM_I2C_SDA_GPIO_PIN;
	GPIO_InitStuctrue.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO\_Init(EEPROM_I2C_SDA_GPIO_PORT,&GPIO_InitStuctrue);
	
	//IIC结构体成员配置
   I2C_InitStuctrue.I2C_Ack=I2C_Ack_Enable;
	I2C_InitStuctrue.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
	I2C_InitStuctrue.I2C_ClockSpeed=EEPROM_I2C_BAUDRATE;
	I2C_InitStuctrue.I2C_DutyCycle=I2C_DutyCycle_2;
	I2C_InitStuctrue.I2C_Mode=I2C_Mode_I2C;
	I2C_InitStuctrue.I2C_OwnAddress1=STM32_I2C_OWN_ADDR;
	I2C\_Init(EEPROM_I2C,&I2C_InitStuctrue);
	I2C\_Cmd(EEPROM_I2C,ENABLE);

}

//向EEPROM写入一个字节
void  EEPROM\_Byte\_Write(uint8\_t addr,uint8\_t data)
{
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	//发送设备写地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
	//检测EV6事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	//发送要操作设备内部的地址
	I2C\_SendData(EEPROM_I2C,addr);
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR);
  I2C\_SendData(EEPROM_I2C,data);
	//检测EV8\_2事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED )==ERROR);
	//发送停止信号
	I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	
}

//向EEPROM写入多个字节
uint32\_t  EEPROM\_Page\_Write(uint8\_t addr,uint8\_t \*data,uint16\_t Num_ByteToWrite)
{
	
	 I2CTimeout = I2CT_LONG_TIMEOUT;
	//判断IIC总线是否忙碌
	while(I2C\_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))   
	{
		if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(1);
	} 
	//重新赋值
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR)
	{
		 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(2);
	} 
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送设备写地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
	//检测EV6事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR)
	{
		 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(3);
	} 

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送要操作设备内部的地址
	I2C\_SendData(EEPROM_I2C,addr);
	//检测EV8事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
	{
		 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(4);
	} 

	while(Num_ByteToWrite)
	{
		I2C\_SendData(EEPROM_I2C,\*data);
		I2CTimeout = I2CT_FLAG_TIMEOUT;
		while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
		{
				if((I2CTimeout--) == 0) return   I2C\_TIMEOUT\_UserCallback(5);
		} 
		 Num_ByteToWrite--;
		 data++;
	}

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//检测EV8\_2事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED )==ERROR)
	{
				if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(6);
	 } 
	//发送停止信号
	I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	 return 1;
}

//向EEPROM读取多个字节
uint32\_t EEPROM\_Read(uint8\_t \*data,uint8\_t addr,uint16\_t Num_ByteToRead)
{
	 I2CTimeout = I2CT_LONG_TIMEOUT;
  //判断IIC总线是否忙碌
  while(I2C\_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))   
  {
    if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(1);
  } 
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT )==ERROR)
  {
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(7);
   } 
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送设备写地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
	//检测EV6事件等待从机应答
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED )==ERROR)
 {
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(8);
  }
  
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送要操作设备内部存储器的地址
	I2C\_SendData(EEPROM_I2C,addr);
	//检测EV8事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
 {
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(9);
  }
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT )==ERROR)
	{
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
   }
	I2CTimeout = I2CT_FLAG_TIMEOUT;	 
	//发送设备读地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Receiver);
	//检测EV6事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR)
	{
       if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
   }
	 
	while(Num_ByteToRead--)
	{
		//是否是最后一个字节,若是则发送非应答信号
		if( Num_ByteToRead==0)
	 {
		 //发送非应答信号
		 I2C\_AcknowledgeConfig(EEPROM_I2C,DISABLE);
		 //发送停止信号
	   I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	 }
	 
	 I2CTimeout = I2CT_FLAG_TIMEOUT;	 
	 //检测EV7事件
   while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED )==ERROR)
  {
       if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
   }
	 
    \*data=I2C\_ReceiveData(EEPROM_I2C);
	  data++; 
	 
	}
	
	//重新开启应答信号
	I2C\_AcknowledgeConfig(EEPROM_I2C,ENABLE);
  return 1;
}
void I2C\_EE\_BufferWrite(uint8\_t\* pBuffer,uint8\_t WriteAddr, uint16\_t NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
  //I2C\_PageSize=8
  Addr = WriteAddr % I2C_PageSize;
  count = I2C_PageSize - Addr;
  NumOfPage =  NumByteToWrite / I2C_PageSize;
  NumOfSingle = NumByteToWrite % I2C_PageSize;
 
  /\* 写入数据的地址对齐,对齐数为8 \*/
  if(Addr == 0) 
  {
    /\* 如果写入的数据个数小于8 \*/
    if(NumOfPage == 0) 
    {
      EEPROM\_Page\_Write(WriteAddr, pBuffer, NumOfSingle);
      EEPROM\_WaitForWriteEnd();
    }
    /\* 如果写入的数据个数大于8 \*/
    else  
    {
			//按页写入
      while(NumOfPage--)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, I2C_PageSize); 
    	  EEPROM\_WaitForWriteEnd();
        WriteAddr +=  I2C_PageSize;
        pBuffer += I2C_PageSize;
      }
      //不足一页(8个)单独写入
      if(NumOfSingle!=0)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, NumOfSingle);
        EEPROM\_WaitForWriteEnd();
      }
    }
  }
  /\*写的数据的地址不对齐\*/
  else 
  {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / I2C_PageSize;
      NumOfSingle = NumByteToWrite % I2C_PageSize;	
      
      if(count != 0)
      {  
        EEPROM\_Page\_Write(WriteAddr, pBuffer, count);
        EEPROM\_WaitForWriteEnd();
        WriteAddr += count;
        pBuffer += count;
      } 
      
      while(NumOfPage--)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, I2C_PageSize);
        EEPROM\_WaitForWriteEnd();
        WriteAddr +=  I2C_PageSize;
        pBuffer += I2C_PageSize;  
      }
      if(NumOfSingle != 0)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, NumOfSingle); 
        EEPROM\_WaitForWriteEnd();
      }
    } 
}

uint32\_t EEPROM\_WaitForWriteEnd(void)
{
	I2CTimeout = I2CT_FLAG_TIMEOUT;	
	
	do
	{
		  I2CTimeout = I2CT_FLAG_TIMEOUT;
			//发送起始信号
			I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
			//检测EV5事件
			while( I2C\_GetFlagStatus(EEPROM_I2C,I2C_FLAG_SB )==RESET)
			{
					 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
			 }
			I2CTimeout = I2CT_FLAG_TIMEOUT;	
			//发送设备写地址
			I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
		
	}while( (I2C\_GetFlagStatus(EEPROM_I2C,I2C_FLAG_ADDR )==RESET) && (I2CTimeout--) );
	
	//发送停止信号
	I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	return 1;
}



static  uint32\_t I2C\_TIMEOUT\_UserCallback(uint8\_t errorCode)
{
  /\* Block communication and all processes \*/
  EEPROM\_ERROR("I2C 等待超时!errorCode = %d",errorCode);
  
  return 0;
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307

main.c

#include "stm32f10x.h"
#include "led.h"
#include "./i2c/i2c\_ee.h"
#include <string.h>
#include "usart.h"
#define SOFT\_DELAY Delay(0x0FFFFF);

void Delay(__IO u32 nCount); 

//声明I2C测试函数
uint8\_t I2C\_EE\_Test(void);
int main(void)
{	
	//初始化IIC
   I2C\_EE\_Config();
   //初始化USART 
   Usart\_Config();
	//初始化LED
   LED\_GPIO\_Config();
	printf("\r\nIIC读写EEPROM测试实验\r\n");
	
	//读写成功亮绿灯,失败亮红灯
   if( I2C\_EE\_Test()==1 )
	 {
		 LED\_G(NO);
	 }
	 else
	 {
		 LED\_R(NO);
	 }
	
while(1)
{
;
}
 
 }
	 uint8\_t I2C\_EE\_Test(void)
	 {	
		  uint8\_t ReadData[256]={0};
      uint8\_t WriteDdta[256]={0};
		  uint16\_t i;
		  //初始化写入数组
		   for(i=0;i<256;i++)
	    {
		    WriteDdta[i]=i; 
	     }
			 //向EEPROM从地址为0开始写入256个字节的数据 
				I2C\_EE\_BufferWrite(WriteDdta,0,256);
				//等待EEPROM写入数据完成 
				EEPROM\_WaitForWriteEnd();	 
			 //向EEPROM从地址为0开始读出256个字节的数据
				EEPROM\_Read(ReadData,0,256);

			 for (i=0; i<256; i++)
				{	
				 if(ReadData[i] != WriteDdta[i])
					{
						EEPROM\_ERROR("0x%02X ", ReadData[i]);
						EEPROM\_ERROR("错误:I2C EEPROM写入与读出的数据不一致\n\r");
						return 0;
					}
					 printf("0x%02X ", ReadData[i]);
					 if(i%16 == 15)    
					 printf("\n\r");   
				}
				EEPROM\_INFO("I2C(AT24C02)读写测试成功\n\r");
				return 1;
	 }

void Delay(__IO uint32\_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

重点讲一下,如何解决以下页写入问题,实现连续写入

  • 进行页写入时,写入的存储器地址要对齐到8,也就是说只能写入地址为 0 8 16 32… 能整除8
  • 页写如只能一次写入8个字节

现在来解释代码中下图函数如何解决问题
在这里插入图片描述

如果地址对齐:
在这里插入图片描述
在这里插入图片描述
如果地址不对齐:
在这里插入图片描述

实验效果

请添加图片描述

七.软件模式I2C协议

实验目的

STM32作为主机向从机EEPROM存储器写入256个字节的数据
STM32作为主机向从机EEPROM存储器读取写入的256个字节的数据

读写成功亮绿灯,读写失败亮红灯

实验原理

在这里插入图片描述
软件模式I2C由我们CPU来控制引脚产生I2C时序,所以我们随便选引脚都可以,不过你选择的引脚肯定要连接到通信的EEPROM的SCL,SDA引脚上。这里是用了PC12,PC11充当主机STM32SCL,SDA引脚。

  • 主机产生起始信号
    在这里插入图片描述
  • 主机产生停止信号
    在这里插入图片描述
  • 主机产生应答信号或非应答信号
    在这里插入图片描述
    在这里插入图片描述
  • 等待从机EEPROM应答

在这里插入图片描述

  • 主机发送一个字节给从机
    在这里插入图片描述
  • 主机向EEPROM接收一个字节
    在这里插入图片描述
    value应该初始化为0,我忘了sorry

源码

i2c_gpio.h

#ifndef \_I2C\_GPIO\_H
#define \_I2C\_GPIO\_H


#include "stm32f10x.h"

#define EEPROM\_I2C\_WR 0 /\* 写控制bit \*/
#define EEPROM\_I2C\_RD 1 /\* 读控制bit \*/

#define EEPROM\_GPIO\_PORT\_I2C GPIOB
#define EEPROM\_RCC\_I2C\_PORT RCC\_APB2Periph\_GPIOB
#define EEPROM\_I2C\_SCL\_PIN GPIO\_Pin\_6
#define EEPROM\_I2C\_SDA\_PIN GPIO\_Pin\_7

/\*当 STM32 的 GPIO 配置成开漏输出模式时,它仍然可以通过读取
GPIO 的输入数据寄存器获取外部对引脚的输入电平,也就是说它同时具有浮空输入模式的
功能\*/

#define EEPROM\_I2C\_SCL\_1() EEPROM\_GPIO\_PORT\_I2C->BSRR |= EEPROM\_I2C\_SCL\_PIN /\* SCL = 1 \*/
#define EEPROM\_I2C\_SCL\_0() EEPROM\_GPIO\_PORT\_I2C->BRR |= EEPROM\_I2C\_SCL\_PIN /\* SCL = 0 \*/
	
#define EEPROM\_I2C\_SDA\_1() EEPROM\_GPIO\_PORT\_I2C->BSRR |= EEPROM\_I2C\_SDA\_PIN /\* SDA = 1 \*/
#define EEPROM\_I2C\_SDA\_0() EEPROM\_GPIO\_PORT\_I2C->BRR |= EEPROM\_I2C\_SDA\_PIN /\* SDA = 0 \*/

#define EEPROM\_I2C\_SDA\_READ() ((EEPROM\_GPIO\_PORT\_I2C->IDR & EEPROM\_I2C\_SDA\_PIN)!=0 ) /\* 读SDA口线状态 \*/


void i2c\_Start(void);
void i2c\_Stop(void);
void i2c\_Ack(void);
void i2c\_NAcK(void);
uint8\_t i2c\_WaitAck(void);
void i2c\_SendByte(uint8\_t data);
uint8\_t i2c\_ReadByte(void);
uint8\_t i2c\_CheckDevice(uint8\_t Address);
#endif /\* \_I2C\_GPIO\_H \*/


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

i2c_gpio.c

#include "i2c\_gpio.h"

#include "stm32f10x.h"

void I2c\_gpio\_config(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC\_APB2PeriphClockCmd(EEPROM_RCC_I2C_PORT, ENABLE);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO\_Init(EEPROM_GPIO_PORT_I2C, &GPIO_InitStructure);
	
	/\* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 \*/
	i2c\_Stop();
}

static void i2c\_Delay(void)
{
	uint8\_t i;
	for(i=0;i<10;i++)
	{
	}
}


void i2c\_Start(void)
{
	EEPROM\_I2C\_SCL\_1();
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
}

void i2c\_Stop(void)
{
	EEPROM\_I2C\_SDA\_0();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();
}

void i2c\_Ack(void)
{
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();

}

void i2c\_NAcK(void)
{
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();

}

uint8\_t i2c\_WaitAck(void)
{
	uint8\_t ret;
	EEPROM\_I2C\_SDA\_1();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	if( EEPROM\_I2C\_SDA\_READ() )
	{
		ret=1;
	}
	else
	{
		ret=0;
	}
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
  return ret;

}
	

void i2c\_SendByte(uint8\_t data)
{
	uint8\_t i;
	for(i=0;i<8;i++)
	{
		if( data&0x80 )
	 {
		  EEPROM\_I2C\_SDA\_1();
	 }
	 else
	 {
		  EEPROM\_I2C\_SDA\_0();
	 }
	 i2c\_Delay();
	 EEPROM\_I2C\_SCL\_1();
	 i2c\_Delay();
	 EEPROM\_I2C\_SCL\_0();
	 i2c\_Delay();
	 if( i==7 )
	 {
		 EEPROM\_I2C\_SDA\_1();
		 i2c\_Delay();
	 }
	 data=data<<1;
	}
	
}

uint8\_t i2c\_ReadByte(void)
{
	uint8\_t value=0;
	uint8\_t i;
	for(i=0;i<8;i++)
	{
		value=value<<1;
		EEPROM\_I2C\_SCL\_1();
	  i2c\_Delay();
		if( EEPROM\_I2C\_SDA\_READ() )
	  {
	 	  value++;
	  }
	  EEPROM\_I2C\_SCL\_0();
	  i2c\_Delay();
	}
	return value;
}

uint8\_t i2c\_CheckDevice(uint8\_t Address)
{
	uint8\_t ucACK;
	I2c\_gpio\_config();
	i2c\_Start();
	i2c\_SendByte(Address|EEPROM_I2C_WR);
	ucACK=i2c\_WaitAck();
	i2c\_Stop();
  return ucACK;	
	
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154

i2c_ee.h

#ifndef \_I2C\_EE\_H
#define \_I2C\_EE\_H


#include "stm32f10x.h"


#define EEPROM\_DEV\_ADDR 0xA0 /\* 24xx02的设备地址 \*/
#define EEPROM\_PAGE\_SIZE 8 /\* 24xx02的页面大小 \*/
#define EEPROM\_SIZE 256 /\* 24xx02总容量 \*/


uint8\_t ee\_Checkok(void);
uint8\_t  ee\_ReadByte( uint8\_t \*pReaddata,uint16\_t Address,uint16\_t num );
uint8\_t  ee\_WriteByte( uint8\_t \*Writepdata,uint16\_t Address,uint16\_t num );
uint8\_t ee\_WaitStandby(void);
uint8\_t ee\_WriteBytes(uint8\_t \*_pWriteBuf, uint16\_t _usAddress, uint16\_t _usSize);
uint8\_t ee\_ReadBytes(uint8\_t \*_pReadBuf, uint16\_t _usAddress, uint16\_t _usSize);
uint8\_t ee\_Test(void) ;
#endif /\* \_I2C\_EE\_H\*/


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

i2c_ee.c

#include "i2c\_ee.h"
#include "i2c\_gpio.h"

//检测EEPORM是否忙碌
uint8\_t ee\_Checkok(void)
{
	if(i2c\_CheckDevice(EEPROM_DEV_ADDR)==0)
	{
		return 1;
	}
	else
	{
    i2c\_Stop();  
		return 0;
 	}
}	
//检测EEPROM写入数完成
uint8\_t ee\_WaitStandby(void)
{
	uint32\_t wait_count = 0;
	
	while(i2c\_CheckDevice(EEPROM_DEV_ADDR))
	{
		//若检测超过次数,退出循环
		if(wait_count++>0xFFFF)
		{
			//等待超时
			return 1;
		}
	}
	//等待完成
	return 0;
}


//向EEPROM写入多个字节
uint8\_t ee\_WriteBytes(uint8\_t \*_pWriteBuf, uint16\_t _usAddress, uint16\_t _usSize)
{
	uint16\_t i,m;
	uint16\_t addr;
	addr=_usAddress;
  for(i=0;i<_usSize;i++)
	{
		  //当第一次或者地址对齐到8就要重新发起起始信号和EEPROM地址
		  //为了解决8地址对齐问题
			if(i==0 || (addr % EEPROM_PAGE_SIZE)==0 )
			{
				 //循环发送起始信号和EEPROM地址的原因是为了等待上一次写入的一页数据\
 写入完成
				 for(m=0;m<1000;m++)
				 {
					 //发送起始地址
					 i2c\_Start();
					 //发送设备写地址
					 i2c\_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_WR);
					 //等待从机应答
					 if( i2c\_WaitAck()==0 )
					 {
						break;
					 }
				 } 
				  //若等待的1000次从机还未应答,等待超时
				  if( m==1000 )
			  	{
					goto cmd_fail;
			   	}	
				//EEPROM应答后发送EEPROM的内部存储器地址
				i2c\_SendByte((uint8\_t)addr);
				//等待从机应答
				if( i2c\_WaitAck()!=0 )
				{
					goto cmd_fail;
					
				}	
			}
		 //发送数据
		 i2c\_SendByte(_pWriteBuf[i]);
		 //等待应答
	   if( i2c\_WaitAck()!=0 )
	   {
		  goto cmd_fail;			
     }
		 //写入地址加1
		 addr++;		
	}
	
	i2c\_Stop();
	return 1;
	
	cmd_fail:
	i2c\_Stop();
	return 0;
}


uint8\_t ee\_ReadBytes(uint8\_t \*_pReadBuf, uint16\_t _usAddress, uint16\_t _usSize)
{
	uint16\_t i;
	
	  i2c\_Start();
		i2c\_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_WR);
	 if( i2c\_WaitAck()!=0 )
	 {
			 goto cmd_fail;		
	  }
		i2c\_SendByte((uint8\_t)_usAddress);
	 if( i2c\_WaitAck()!=0 )
	 {
			  goto cmd_fail;
	  }
		i2c\_Start();
		i2c\_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_RD);
		 if( i2c\_WaitAck()!=0 )
		 {
				  goto cmd_fail;				
	   }
	 for(i=0;i<_usSize;i++)
	{	
		_pReadBuf[i]=i2c\_ReadByte();
		/\* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack \*/
		if (i != _usSize - 1)
		{
// i2c\_NAcK(); /\* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) \*/
			i2c\_Ack();	/\* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) \*/
		}
		else
		{
			i2c\_NAcK();	/\* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) \*/
		}
	}
	i2c\_Stop();
	return 1;
	
	cmd_fail:
	i2c\_Stop();
	return 0;
}

uint8\_t ee\_Test(void) 
{
  uint16\_t i;
	uint8\_t write_buf[EEPROM_SIZE];
  uint8\_t read_buf[EEPROM_SIZE];
  
/\*-----------------------------------------------------------------------------------\*/  
  if (i2c\_CheckDevice(EEPROM_DEV_ADDR) == 1)
	{
		/\* 没有检测到EEPROM \*/
		printf("没有检测到串行EEPROM!\r\n");
				
		return 0;
	}
/\*------------------------------------------------------------------------------------\*/  
  /\* 填充测试缓冲区 \*/
	for (i = 0; i < EEPROM_SIZE; i++)
	{		
		write_buf[i] = i;
	}
/\*------------------------------------------------------------------------------------\*/  
  if (ee\_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
	{
		printf("写EEPROM出错!\r\n");
		return 0;
	}
	else
	{		
		printf("写EEPROM成功!\r\n");
	}  

/\*-----------------------------------------------------------------------------------\*/
  if (ee\_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
	{
		printf("EEPROM出错!\r\n");
		return 0;
	}
	else
	{		
		printf("EEPROM成功,数据如下:\r\n");
	}
/\*-----------------------------------------------------------------------------------\*/  
  for (i = 0; i < EEPROM_SIZE; i++)
	{
		if(read_buf[i] != write_buf[i])
		{
			printf("0x%02X ", read_buf[i]);
			printf("错误:EEPROM读出与写入的数据不一致");
			return 0;
		}
    printf(" %02X", read_buf[i]);
		
		if ((i & 15) == 15)
		{
			printf("\r\n");	
		}		
	}
  printf("EEPROM读写测试成功\r\n");
  return 1;
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201

main

#include "stm32f10x.h"


![img](https://img-blog.csdnimg.cn/img_convert/f754a0b6dc156951eda27688b81a0e5d.png)
![img](https://img-blog.csdnimg.cn/img_convert/3955ab88c1c22f530954a7631bc7ffc7.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

	
		printf("写EEPROM成功!\r\n");
	}  

/\*-----------------------------------------------------------------------------------\*/
  if (ee\_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
	{
		printf("EEPROM出错!\r\n");
		return 0;
	}
	else
	{		
		printf("EEPROM成功,数据如下:\r\n");
	}
/\*-----------------------------------------------------------------------------------\*/  
  for (i = 0; i < EEPROM_SIZE; i++)
	{
		if(read_buf[i] != write_buf[i])
		{
			printf("0x%02X ", read_buf[i]);
			printf("错误:EEPROM读出与写入的数据不一致");
			return 0;
		}
    printf(" %02X", read_buf[i]);
		
		if ((i & 15) == 15)
		{
			printf("\r\n");	
		}		
	}
  printf("EEPROM读写测试成功\r\n");
  return 1;
}



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

main

#include "stm32f10x.h"


[外链图片转存中...(img-iOLQcz0T-1715530888210)]
[外链图片转存中...(img-XPEcuprk-1715530888210)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/1004318
推荐阅读
相关标签
  

闽ICP备14008679号