IIC通讯试验
准备资源:
1)GD32F307C-EVL开发板一块
2)232串口线一根
试验目的:
实现GPIO软件模拟IIC通讯,通过IIC将一个字节数据写入EEPROM以及通过IIC读取该字节并通过串口打印出来。
硬件原理图
首先需要的就是查看开发板硬件原理图,可以看到使用的EEPROM为AT24C02,其SCL、SDA管脚分别于MCU的PB6、PB7连接。所以可以使用PB6、PB7这两个GPIO口进行软件模拟IIC通讯。
实现代码一myi2c.c文件
根据IIC通讯协议,使用GPIO模拟出IIC起始信号、停止信号等一系列信号
1)首先需要对GPIO的PB6、PB7初始化,其中PB6模拟SCL信号线,PB7模拟SDA信号线。初始函数如下:
void myi2c_init(void)
{
rcu_periph_clock_enable(RCU_GPIOB);
gpio_init(GPIOB,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_6|GPIO_PIN_7);
IIC_SCL(1);
IIC_SDA(1);
}
2)在myi2c.h头文件中需要添加以下宏定义,以控制PB6、PB7的输入输出方向
#define SDA_IN() {GPIO_CTL0(GPIOB)&=~GPIO_MODE_MASK(7);GPIO_CTL0(GPIOB)|=GPIO_MODE_SET(7,0x8);}//PB9输入模式
#define SDA_OUT() {GPIO_CTL0(GPIOB)&=~GPIO_MODE_MASK(7);GPIO_CTL0(GPIOB)|=GPIO_MODE_SET(7,0x3);}//PB9输出模式
#define IIC_SCL(n) (n?gpio_bit_set(GPIOB,GPIO_PIN_6):gpio_bit_reset(GPIOB,GPIO_PIN_6))//n=1,输出高电平;n=0,输出低电平
#define IIC_SDA(n) (n?gpio_bit_set(GPIOB,GPIO_PIN_7):gpio_bit_reset(GPIOB,GPIO_PIN_7))
#define READ_SDA gpio_input_bit_get(GPIOB,GPIO_PIN_7)
3)由于GD官方给的库中只有一个ms延时函数,故需要添加一个us延时函数,如下:
/* 描述:us级延时函数
* 参数nus:需要延时的us数
* 返回值:无*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = SysTick->LOAD; /* 滴答定时器的重装载值 */
ticks = nus * 120; /* 需要的节拍数 */
told = SysTick->VAL; /* 刚进入时的计数器值 */
while(1)
{
tnow = SysTick->VAL;
if(tnow != told)
{
if(tnow < told)tcnt += told - tnow;
else tcnt += reload - tnow + told;
if(tcnt >= ticks)break; /* 时间超过/等于要延迟的时间,则退出. */
told = tnow;
}
}
}
4)完成GPIO的初始化之后,便可以进行软件模拟IIC。代码如下:
//IIC起始信号
void i2c_sart(void)
{
SDA_OUT();//SDA输出模式
IIC_SCL(1);
IIC_SDA(1);
delay_us(4);
IIC_SDA(0);
delay_us(4);
IIC_SCL(0);
}
//IIC停止信号
void i2c_stop(void)
{
SDA_OUT();
IIC_SCL(0);
IIC_SDA(0);
delay_us(4);
IIC_SCL(1);
delay_us(4);
IIC_SDA(1);
delay_us(4);
}
/
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
uint8_t i2c_wait_ack(void)
{
uint8_t ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA(1);delay_us(1);
IIC_SCL(1);delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
i2c_stop();
return 1;
}
}
IIC_SCL(0);//时钟输出0
return 0;
}
//产生ACK应答
void i2c_ack(void)
{
IIC_SCL(0);
SDA_OUT();
IIC_SDA(0);
delay_us(2);
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
}
//不产生ACK应答
void i2c_nack(void)
{
IIC_SCL(0);
SDA_OUT();
IIC_SDA(1);
delay_us(2);
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void i2c_send_byte(uint8_t byte)
{
uint8_t t;
SDA_OUT();
IIC_SCL(0);//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if(byte&0x80) IIC_SDA(1);
else IIC_SDA(0);
byte<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK; ack=0,发送nACK
uint8_t i2c_read_byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL(0);
delay_us(2);
IIC_SCL(1);
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
i2c_nack();//发送nACK
else
i2c_ack(); //发送ACK
return receive;
}
实现代码一24cxx.c文件
试验使用的开发板上的EEPROM为AT24C02,是一个2K Bit的串行EEPROM存储器,内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。
下图所示为AT24Cxx系列EEPROM的设备地址,由8位组成,其中A0、A1、A2三位加上高四位构成设备地址,最低一位控制读写方向。具体可查看数据手册。
这里只实现通过IIC向AT24C02读写一个字节的简单操作,其他多个字节的读写可在实际应用中进行扩展。代码如下:
void AT24CXX_Init(void) //初始化IIC
{
myi2c_init();
}
//指定地址读取一个字节:
//启动总线-->发送设备地址+写-->等待应答-->发送数据存储地址-->
//等待应答-->发送设备地址+读-->等待应答-->读取数据-->主机发送是否应答……
uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr)
{
uint8_t temp;
i2c_sart();
i2c_send_byte(0xA0 + ((ReadAddr/256)<<1));//发送设备地址0xA0 + 写
i2c_wait_ack();
i2c_send_byte(ReadAddr%256); //发送低地址
i2c_wait_ack();
i2c_sart();
i2c_send_byte(0xA1);
i2c_wait_ack();
temp=i2c_read_byte(0); //发送nACK
i2c_stop();
return temp;
}
//指定地址写入一个字节:
//启动总线-->发送设备地址+写-->等待应答-->
//发送数据的储存地址(0x00-0xFF,256个字节-->等待应答-->发送数据-->等待应答-->停止总线
void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite)
{
i2c_sart();
i2c_send_byte(0xA0 + ((WriteAddr/256)<<1));//发送设备地址0xA0 + 写
i2c_wait_ack();
i2c_send_byte(WriteAddr%256); //发送低地址
i2c_wait_ack();
i2c_send_byte(DataToWrite);
i2c_wait_ack();
i2c_stop();
delay_1ms(10);
}
实现代码一main.c
main函数:通过检测两个按键是否按下,当按键KEY_TAMPER按下则从0x00地址开始写一个字节至AT24C02,当按键KEY_USER按下则从0x00地址读一个字节,并通过串口打印出来。
代码如下:
int main(void)
{
uint8_t datatemp;
systick_config();
gd_eval_com_init(EVAL_COM1);
AT24CXX_Init();
gd_eval_key_init(KEY_TAMPER, KEY_MODE_GPIO);
gd_eval_key_init(KEY_USER, KEY_MODE_GPIO);
printf("\r\n IIC test... \r\n");
while(1){
if(RESET == gd_eval_key_state_get(KEY_TAMPER))
{ //未进行按键的防抖
printf("\r\n Start Write 24C02.... \r\n");
AT24CXX_WriteOneByte(0x00,0x0a);
printf("\r\n 24C02 Write Finished! \r\n");
}
if(RESET == gd_eval_key_state_get(KEY_USER))
{
printf("\r\n Start read 24C02.... \r\n");
datatemp = AT24CXX_ReadOneByte(0x00);
printf("datatemp is : 0x%x \r\n",datatemp);
}
}
}
结果
1)按键KEY_TAMPER按下
2)按键KEY_USER按下