嵌入式_基于STM32的软件IIC实现
本文是基于STM32标准库的IIC实现,并再此基础上实现了AT24C02的读写,关于IIC的原理简介请参见上篇IIC协议理解,
前言
基于STM32F407 标准库的软件IIC实现方式,亲测可用。
提示:以下是本篇文章正文内容,下面案例可供参考
一、头文件简介
主要定义SDA和SCL的引脚定义,输入输出模式,高低电平函数,延时函数等
#ifndef __I2C_S_H
#define __I2C_S_H
#include "Gpio_Dev.h" //包含初始化GPIO,初始化时钟
/*******************************************
*Soft I2C
********************************************/
#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}//PB9 输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;}//PB9 输出模式
#define SW_I2C_SCL_PORT GPIOB
#define SW_I2C_SCL_PIN GPIO_Pin_8
#define SW_I2C_SDA_PORT GPIOB
#define SW_I2C_SDA_PIN GPIO_Pin_9
#define I2C_WAIT_TIMEOUT (uint8_t)250 //等待时间
#define I2C_DELAY (uint8_t)5 //延时时间
#define I2C_N_ACK (0U) //不发ACK
#define I2C_ACK (1U) //发送ACK
#define SW_I2C_SCL_LOW GPIO_ResetBits(SW_I2C_SCL_PORT,SW_I2C_SCL_PIN)
#define SW_I2C_SCL_HIGH GPIO_SetBits(SW_I2C_SCL_PORT,SW_I2C_SCL_PIN)
#define SW_I2C_SDA_LOW GPIO_ResetBits(SW_I2C_SDA_PORT,SW_I2C_SDA_PIN)
#define SW_I2C_SDA_HIGH GPIO_SetBits(SW_I2C_SDA_PORT,SW_I2C_SDA_PIN)
#define I2C_SDA_STATUS GPIO_ReadInputDataBit(SW_I2C_SDA_PORT,SW_I2C_SDA_PIN) //读取SDA状态
#define I2C_SCL_STATUS GPIO_ReadInputDataBit(SW_I2C_SCL_PORT,SW_I2C_SCL_PIN) //读取SCL状态
#define SW_I2C_SDA_INPUT sw_i2c_set_sda_input() //设置SDA为输入模式
#define SW_I2C_SDA_OUTPUT sw_i2c_set_sda_output() //设置SDA为输出模式
#define SW_I2C_SDA_STATUS sw_i2c_sda_status() //设置SDA状态
#define SW_I2C_BusInit sw_i2c_busInit() //总线初始化
#define i2c_delay_us(a) SysCtlDelayus(a) //延时函数us
extern void sw_i2c_start(void);
extern void sw_i2c_stop(void);
extern void Send_Ack(void);
extern void Send_N_Ack(void);
extern uint8_t sw_i2c_wait_ack(void);
extern void I2C_Write_Byte(uint8_t aByte);
extern uint8_t I2C_Read_Byte(uint8_t Ack);
extern ReturnType I2C_Send_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen);
extern ReturnType I2C_Read_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen);
#endif
二、延时函数
IIC延时采用的汇编延时,延时精准,如下为汇编延时函数:
该函数由三条汇编指令构成,
当参数ulcount = 1时,只执行一次三条汇编指令,
执行一条汇编指令时间为:1/系统时钟(MHz),所以ulcount = 1时,延时为delayus = (1/SystemCoreClock(MHz)* 3)us
当参数ulcount = n时,执行n次三条汇编指令,
执行一条汇编指令时间为:1/系统时钟(MHz),所以ulcount = n时,延时为delayus = (1/SystemCoreClock (MHz)* 3 * n)us
函数void SysCtlDelayus(unsigned long ulCount),参数表示延时多少us
计算方法:系统中SystemCoreClock为CPU时钟频率,单位为Hz,所以SystemCoreClock(MHz)= SystemCoreClock(Hz)/1000000
根据上述,
因为延时 (1/SystemCoreClock(MHz)* 3)us需要ucount = 1,
所以延时1us,需要ucount = 1/(1/SystemCoreClock(MHz)* 3),
根据计算方法所述,带入公式,延时1us,需要ucount = 1/(1/(SystemCoreClock(Hz)/1000000)* 3)
化简得:
延时1 us ucount = SystemCoreClock(Hz)/3000000)
延时x us ucount = x * SystemCoreClock(Hz)/3000000),得到函数void SysCtlDelayus(unsigned long ulCount),
用示波器测试,延时比较精准,在使用前需要明确板子时钟是以兆为时钟单位,可打开或关闭系统中断会更加精准
/***************************************************
*@brief:SysTickDelayMs
*@author:Yw 2022-02-12
*@param:None
*@retval:None
****************************************************/
#if defined (__CC_ARM) /*!< ARM Compiler */
/*
delayus = (1/SystemCoreClock * 3 * ulCount)us
*/
__ASM volatile void SysCtlDelay(unsigned long ulCount)
{
subs r0, #1;
bne SysCtlDelay;
bx lr;
}
void SysCtlDelayus(unsigned long ulCount)
{
//CPU_INI_DISABLE(); //关闭系统中断
SysCtlDelay(ulCount * (SystemCoreClock/3000000));
//CPU_INI_ENABLE(); //打开系统中断
}
#endif /* __CC_ARM */
三、函数实现
1.起始信号和结束信号
代码如下(示例):
/************************************************************************************
*@fuction :sw_i2c_start
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void sw_i2c_start(void)
{
//I2C开始时序:SDL输出模式 SDL = 1 SCL = 1时,SDA由1变成0.
SDA_OUT();
SW_I2C_SDA_HIGH;
i2c_delay_us(I2C_DELAY);
SW_I2C_SCL_HIGH;
i2c_delay_us(I2C_DELAY);
SW_I2C_SDA_LOW;
i2c_delay_us(I2C_DELAY);
}
/************************************************************************************
*@fuction :sw_i2c_stop
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void sw_i2c_stop(void)
{
//I2C 停止时序:SDL输出模式 SDL = 0 SCL = 1时,SDA由0变成1.
SDA_OUT();
SW_I2C_SDA_LOW;
i2c_delay_us(I2C_DELAY);
SW_I2C_SCL_HIGH;
i2c_delay_us(I2C_DELAY);
SW_I2C_SDA_HIGH;
}
2.发送ACK与无ACK
代码如下(示例):
/************************************************************************************
*@fuction :Send_Ack
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void Send_Ack(void)
{
//发送ACK就是在SDA=0时,SCL由0变成1
SDA_OUT();
SW_I2C_SCL_LOW;
SW_I2C_SDA_LOW;
i2c_delay_us(I2C_DELAY);
SW_I2C_SCL_HIGH;
i2c_delay_us(I2C_DELAY);
SW_I2C_SCL_LOW;
i2c_delay_us(I2C_DELAY);
}
/************************************************************************************
*@fuction :Send_N_Ack
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void Send_N_Ack(void)
{
//发送nACK就是在SDA=1时,SCL由0变成1
SDA_OUT();
SW_I2C_SCL_LOW;
SW_I2C_SDA_HIGH;
i2c_delay_us(I2C_DELAY);
SW_I2C_SCL_HIGH;
i2c_delay_us(I2C_DELAY);
SW_I2C_SCL_LOW;
i2c_delay_us(I2C_DELAY);
}
3.等待ACK
/************************************************************************************
*@fuction :sw_i2c_wait_ack
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
uint8_t sw_i2c_wait_ack(void)
{
/*IIC总线上接收方在完成接收一个字节后(8bit),在第9个SCL时钟脉冲的上升沿期间,通过SDA响应ACK(SDA=0)或NACK(SDA=1)信号*/
uint8_t wait_time = 0;
uint8_t ack_nack = 1;
//SDA输入模式
SDA_IN();
//等待SDA脚被从机拉低
while(I2C_SDA_STATUS)
{
wait_time++;
if(wait_time >= I2C_WAIT_TIMEOUT)
{
//如果等待时间超时,则退出等待
ack_nack = 0;
break;
}
}
//SCL由0变为1,读入ACK状态
//如果此时SDA=0,则是ACK
//如果此时SDA=1,则是NACK
i2c_delay_us(I2C_DELAY);
SW_I2C_SCL_HIGH;
i2c_delay_us(I2C_DELAY);
//再次将SCL=0,并且将SDA设置为输出
SW_I2C_SCL_LOW;
i2c_delay_us(I2C_DELAY);
SDA_OUT();
i2c_delay_us(I2C_DELAY);
//返回ACK状态
return ack_nack;
}
4.读写一个字节
/************************************************************************************
*@fuction :I2C_Write_Byte
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void I2C_Write_Byte(uint8_t aByte)
{
//写模式:在SCKL = 0时候,可改变SDA状态
uint8_t i = 0;
//将SDA设置为输出模式
SDA_OUT();
for(i = 0;i < 8;i++)
{
//先将SCL拉低,再改变SDA上的数据
SW_I2C_SCL_LOW;
i2c_delay_us(I2C_DELAY);
if(aByte&0x80)
{
//最高位为1
SW_I2C_SDA_HIGH;
}
else
{ //最高位为2
SW_I2C_SDA_LOW;
}
i2c_delay_us(I2C_DELAY);
//将SCL拉高,数据稳定,等待读取
SW_I2C_SCL_HIGH;
i2c_delay_us(I2C_DELAY);
//数据左移一位,下一循环将下一个bit发送出去
aByte <<= 1;
}
//最后字节完成,将SCL拉低
SW_I2C_SCL_LOW;
i2c_delay_us(I2C_DELAY);
}
/************************************************************************************
*@fuction :I2C_Read_Byte
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
uint8_t I2C_Read_Byte(uint8_t Ack)
{
//读模式:在SCKL = 1时候,可读取SDA状态
uint8_t i = 0 ,aByte;
//SDA为输入模式
SDA_IN();
for (i = 0; i < 8; i++)
{
aByte <<= 1;
SW_I2C_SCL_LOW;
i2c_delay_us(I2C_DELAY);
//将SCL拉高,读取数据稳定
SW_I2C_SCL_HIGH;
i2c_delay_us(I2C_DELAY);
if(I2C_SDA_STATUS)
{
aByte |= 0x01;
}
//拉低SCL,解除锁定
SW_I2C_SCL_LOW;
}
//读取一个字节,根据实际情况,是否发送ACK
SDA_OUT();
if(Ack)
{
Send_Ack();
}
else
{
Send_N_Ack();
}
return aByte;
}
5.读写n字节
/************************************************************************************
*@fuction :I2C_Send_nByte
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
ReturnType I2C_Send_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen)
{
ReturnType ret = E_OK;
uint16_t i;
//如果使用的是7bit地址,需要左位移一位
#ifdef I2C_USE_7BIT_ADDR
I2C_Address <<= 1;
#endif
//启动I2C
sw_i2c_start();
//写I2C地址,
I2C_Write_Byte((uint8_t)I2C_Address);
//等待ACK
if(!sw_i2c_wait_ack())
{
//无ACK,则发送结束信号并退出
sw_i2c_stop();
ret = E_NOT_OK;
}
else
{
//有ACK,则继续发送
for (i = 0; i < Datalen; i++)
{
I2C_Write_Byte(Data[i]);
if(!sw_i2c_wait_ack())
{
sw_i2c_stop();
ret = E_NOT_OK;
break;
}
}
}
sw_i2c_stop();
return ret;
}
/************************************************************************************
*@fuction :I2C_Read_nByte
*@brief :
*@param :--
*@return :uint8_t
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
ReturnType I2C_Read_nByte(uint8_t I2C_Address,uint8_t *Data, uint16_t Datalen)
{
ReturnType ret = E_OK;
uint16_t i;
//如果使用的是7bit地址,需要左位移一位
#ifdef I2C_USE_7BIT_ADDR
I2C_Address <<= 1;
#endif
//启动I2C
sw_i2c_start();
//写I2C地址
I2C_Write_Byte((uint8_t)I2C_Address);
//等待ACK
if(!sw_i2c_wait_ack())
{
//无ACK,则发送结束信号并退出
sw_i2c_stop();
ret = E_NOT_OK;
}
else
{ //有ACK,则继续接收
for(i = 0; i < (Datalen-1); i++)
{
//直到倒数第二个字节,都有ACK回复
Data[i] = I2C_Read_Byte(I2C_ACK);
}
}
//最后一个字节,无ACK回复,标识不再接收数据
Data[Datalen-1] = I2C_Read_Byte(I2C_N_ACK);
//发送停止信号
sw_i2c_stop();
return ret;
}
总结
以上就是基于STM32F103的标准库软件IIC操作,便携易操作。
如有错误,欢迎指正,原创不易,转载留名!