基于STM32F103C8T6的多路IIC程序

基于STM32F103C8T6的HAL库多路模拟IIC库,改编自正点原子的模拟IIC代码。

可以用于一些需要多路IIC读取的,尤其是一些不能修改IIC地址的瓜娃子传感器IC的读取。

没错,AS5600,说的就是你!!!

目前这个库支持两路,也就是四个引脚,后续有空还可能扩展一下。

使用方法:

每个函数都有SDA_Channel和SCL_Channel两个参数,对应引脚编号。

而GPIO的端口则通过宏定义设定。

举例:

#define SDA_GPIO GPIOA
#define SCL_GPIO GPIOA

IIC_Start(0,1);                //发送IIC开始信号

则代表这个IIC的起始信号在SDA=PA0,SCL=PA1的信道上传输。

使用时注意别把各IIC通道搞混,对某一路IIC进行读写的函数应使SDA_Channel和SCL_Channel两个参数保持一致,否则会造成通信失败。

myiic.h:

#ifndef _MYIIC_H
#define _MYIIC_H
#include "sys.h"
#include "main.h"
#include "stdio.h"
#include "usart.h"
/*如果需要修改引脚则修改以下区域函数*/
/*始*/
#define SDA_GPIO GPIOA
#define SCL_GPIO GPIOA
void SDA_IN(u8 channle);
void SDA_OUT(u8 channle);
#define IIC_SCL(n)   PAout(n) //SCL
#define IIC_SDA(n)   PAout(n) //SDA
#define READ_SDA(n)  PAin(n)  //输入SDA
void IIC_Init(u8 channel);                //初始化IIC的IO口	
/*末*/
//IIC所有操作函数			 
void IIC_Start(u8 SDA_channel,u8 SCL_channel);				//发送IIC开始信号
void IIC_Stop(u8 SDA_channel,u8 SCL_channel);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd,u8 SDA_channel,u8 SCL_channel);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack,u8 SDA_channel,u8 SCL_channel);//IIC读取一个字节
u8 IIC_Wait_Ack(u8 SDA_channel,u8 SCL_channel); 				//IIC等待ACK信号
void IIC_Ack(u8 SDA_channel,u8 SCL_channel);					//IIC发送ACK信号
void IIC_NAck(u8 SDA_channel,u8 SCL_channel);				//IIC不发送ACK信号
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);	 

#endif

myiic.c

#include "myiic.h"
#include "delay.h"
//IIC初始化
void IIC_Init(u8 channel)
{   
	IIC_SDA(channel)=1;
}
void SDA_IN(u8 channle)
{
	switch (channle)
	{
		case 0:
		{SDA_GPIO->CRL&=0XFFFFFFF0;break;}
		case 1:
		{SDA_GPIO->CRL&=0XFFFFFF0F;break;}
		case 2:
		{SDA_GPIO->CRL&=0XFFFFF0FF;break;}
		case 3:
		{SDA_GPIO->CRL&=0XFFFF0FFF;break;}
	}
	SDA_GPIO->CRL|=(u32)8<<(4*channle);
}
void SDA_OUT(u8 channle)
{
		switch (channle)
	{
		case 0:
		{SDA_GPIO->CRL&=0XFFFFFFF0;break;}
		case 1:
		{SDA_GPIO->CRL&=0XFFFFFF0F;break;}
		case 2:
		{SDA_GPIO->CRL&=0XFFFFF0FF;break;}
		case 3:
		{SDA_GPIO->CRL&=0XFFFF0FFF;break;}
	}
	SDA_GPIO->CRL|=(u32)3<<(4*channle);
}
//产生IIC起始信号
void IIC_Start(u8 SDA_channel,u8 SCL_channel)
{
	SDA_OUT(SDA_channel);     //sda线输出
	IIC_SDA(SDA_channel)=1;	  	  
	IIC_SCL(SCL_channel)=1;
	delay_us(4);
 	IIC_SDA(SDA_channel)=0;//START:when CLK is high,DATA change form high to low 
	delay_us(4);
	IIC_SCL(SCL_channel)=0;//钳住I2C总线,准备发送或接收数据 
}	  
//产生IIC停止信号
void IIC_Stop(u8 SDA_channel,u8 SCL_channel)
{
	SDA_OUT(SDA_channel);//sda线输出
	IIC_SCL(SCL_channel)=0;
	IIC_SDA(SDA_channel)=0;//STOP:when CLK is high DATA change form low to high
 	delay_us(4);
	IIC_SCL(SCL_channel)=1; 
	IIC_SDA(SDA_channel)=1;//发送I2C总线结束信号
	delay_us(4);							   	
}
//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(u8 SDA_channel,u8 SCL_channel)
{
	u8 ucErrTime=0;
	SDA_IN(SDA_channel);      //SDA设置为输入  
	IIC_SDA(SDA_channel)=1;delay_us(1);	   
	IIC_SCL(SCL_channel)=1;delay_us(1);	 
	while(READ_SDA(SDA_channel))
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop(SDA_channel,SCL_channel);
			return 1;
		}
	}
	IIC_SCL(SCL_channel)=0;//时钟输出0 	   
	return 0;  
} 
//产生ACK应答
void IIC_Ack(u8 SDA_channel,u8 SCL_channel)
{
	IIC_SCL(SCL_channel)=0;
	SDA_OUT(SDA_channel);
	IIC_SDA(SDA_channel)=0;
	delay_us(2);
	IIC_SCL(SCL_channel)=1;
	delay_us(2);
	IIC_SCL(SCL_channel)=0;
}
//不产生ACK应答		    
void IIC_NAck(u8 SDA_channel,u8 SCL_channel)
{
	IIC_SCL(SCL_channel)=0;
	SDA_OUT(SDA_channel);
	IIC_SDA(SDA_channel)=1;
	delay_us(2);
	IIC_SCL(SCL_channel)=1;
	delay_us(2);
	IIC_SCL(SCL_channel)=0;
}					 				     
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void IIC_Send_Byte(u8 txd,u8 SDA_channel,u8 SCL_channel)
{                        
    u8 t;   
	SDA_OUT(SDA_channel); 	    
    IIC_SCL(SCL_channel)=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        IIC_SDA(SDA_channel)=(txd&0x80)>>7;
        txd<<=1; 	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL(SCL_channel)=1;
		delay_us(2); 
		IIC_SCL(SCL_channel)=0;	
		delay_us(2);
    }	 
} 	    
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack,u8 SDA_channel,u8 SCL_channel)
{
	unsigned char i,receive=0;
	SDA_IN(SDA_channel);//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        IIC_SCL(SCL_channel)=0; 
        delay_us(2);
		IIC_SCL(SCL_channel)=1;
        receive<<=1;
        if(READ_SDA(SDA_channel))receive++;   
		delay_us(1); 
    }					 
    if (!ack)
        IIC_NAck(SDA_channel,SCL_channel);//发送nACK
    else
        IIC_Ack(SDA_channel,SCL_channel); //发送ACK   
    return receive;
}


再附上AS5600的多路读取代码:

主函数片段:整个工程由CUBEMX创建,所以引脚的初始化函数交给CUBEMX直接生成了。


int main(void)
{
  /* USER CODE BEGIN 1 */
	double Angle1=0,Angle2=0,Angle3=0;
	u32 Base_Angle;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
	HAL_DeInit();
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1, (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
  IIC_Init(0|1|2|3);
	Base_Angle=Get_Ini_Val(0,1);
	HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1|TIM_CHANNEL_2|TIM_CHANNEL_3|TIM_CHANNEL_4);//PWM开启

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		HAL_Delay(100);
		HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
		
		Base_Angle=Get_Ini_Val(0,1);
		printf("通道1原始角度数据:%d,实际角度%.4f\r\n",Base_Angle,Base_Angle*360.0/4095);
		Base_Angle=Get_Ini_Val(2,3);
		printf("通道2原始角度数据:%d,实际角度%.4f\r\n",Base_Angle,Base_Angle*360.0/4095);
		//Duoji_Set_Angle();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

AS5600代码:该部分代码改编自:AS5600步进电机编码器(原理图+pcb+stm32控制代码)_WJB_MR的博客-CSDN博客

AS5600.c

#include "AS5600.h"
#include "myiic.h"
#include "delay.h"


u32 angle_ini = 0; 		   //初始角度值
u32 temp0 = 0;			   //初始角度原始输出值
u32 temp1 = 0;		       //上次角度原始输出值
u32 temp2 = 0;		       //这次角度原始输出值
u32 temp_add = 0;	       //从初始角度开始的累计角度原始值
u8  buf[2] = {0};	       //用于oled显示存放变量,和本程序关系不大
int sign_angle = 0;	       //过零点标记(即从0度转到360度之后继续转又回零的那个点,正向经过一次加一,反向经过一次减一)
double True_Angle = 0.0;   //真实角度(累计角度)
int dir = 0; //0正向 1反向  //方向
double Current_Angle = 0;   //当前角度(就是绝对位置角度,小于360度)


u16 AS5600_Read_Len (u8 addr,u8 reg,u8 len,u8 *buf,u8 SDA_channel,u8 SCL_channel )
{
    IIC_Start(SDA_channel,SCL_channel);
    IIC_Send_Byte((addr<<1)|Write_Bit,SDA_channel,SCL_channel );

    if(IIC_Wait_Ack(SDA_channel,SCL_channel))
    {
        IIC_Stop(SDA_channel,SCL_channel);
        return 1;
    }
    IIC_Send_Byte(reg,SDA_channel,SCL_channel );
    IIC_Wait_Ack(SDA_channel,SCL_channel);
    IIC_Start(SDA_channel,SCL_channel);
    IIC_Send_Byte((addr<<1)|Read_Bit,SDA_channel,SCL_channel);  // 发送器件地址 + 读命令
    IIC_Wait_Ack(SDA_channel,SCL_channel); // 等待应答

    while ( len )
    {
        if ( len == 1 )
            *buf = IIC_Read_Byte ( 0,SDA_channel,SCL_channel);  // 读数据,发送nACK
        else
            *buf = IIC_Read_Byte (1,SDA_channel,SCL_channel) & 0x000f; // 读数据,发送ACK 原始1f,改为0f
        len--;
        buf++;
    }
    IIC_Stop(SDA_channel,SCL_channel);
    return 0;
}

u32 Get_Ini_Val(u8 SDA_channel,u8 SCL_channel )//获得初始角度
{
    u8 i=0;
    u32 transfer=0;
    for ( i = 0; i < 20; i++ )
    {   // 刚开始数据可能不稳定,直接丢掉
        AS5600_Read_Len(Slave_Addr,Angle_Hight_Register_Addr,2,buf,SDA_channel,SCL_channel );
        delay_ms(5);
    }
    for (i=0; i<20; i++)
    {   // 软件滤波
        AS5600_Read_Len(Slave_Addr,Angle_Hight_Register_Addr,2,buf,SDA_channel,SCL_channel);
        transfer+=((buf[0]<<8)|buf[1]);
        delay_ms (5);
    }
    temp0 = (transfer / 20);
    return temp0;
}


//void Get_Temp_Add(void)//计算角度增量
//{

//    if(sign_angle == 0)  //当从没经过零点时
//    {
//        if(temp2 >= temp0) //正转
//        {   temp_add = temp2 - temp0;
//            dir = 0;
//        }
//        else    			//反转
//        {   temp_add = temp0 - temp2;
//            dir = 1;
//        }
//    }
//    else if(sign_angle > 0)//经过一次及以上零点位置后,分两种情况,正向经过与反向经过,需分开讨论
//    {

//        temp_add = 4096 + temp2 - temp0 + ( sign_angle - 1)*4096;  //正向经过
//        dir = 0;
//    }
//    else
//    {
//        temp_add =4096 + temp0 - 4096*(sign_angle+1) - temp2;   //反向经过
//        dir = 1;
//    }
//}

//void Get_Num_sign(void) //计算过零点次数,这个函数也可以用定时器中断来调用,效果更好
//{
//    u32 x;
//    AS5600_Read_Len ( Slave_Addr, Angle_Hight_Register_Addr, 2, buf );
//    temp2 = ( ( buf[0] << 8 ) | buf[1] );
//    if(temp1 >= temp2)
//    {
//        x = temp1 - temp2;
//        if(x>2048)//正转通过零点
//        {
//            sign_angle++;
//        }
//    }
//    else
//    {
//        x = temp2 -temp1;
//        if(x>2048)//反转通过零点
//        {
//            sign_angle--;
//        }
//    }
//    temp1 = temp2;//每次都把temp2赋给temp1
//}



AS5600.h

#ifndef __AS5600__
#define __AS5600__
#include "sys.h"
#include "main.h"

#define Slave_Addr                0x36 //设备从地址
#define Write_Bit                 0	   //写标记	
#define Read_Bit                  1    //读标记
#define Angle_Hight_Register_Addr 0x0C //寄存器高位地址
#define Angle_Low_Register_Addr   0x0D //寄存器低位地址

extern u32 angle_ini; 		   //初始角度值
extern u32 temp0;			   //初始角度原始输出值
extern u32 temp1;		       //上次角度原始输出值
extern u32 temp2;		       //这次角度原始输出值
extern u32 temp_add;	       //从初始角度开始的累计角度原始值
u16 AS5600_Read_Len ( u8 addr, u8 reg, u8 len, u8 *buf,u8 SDA_channel,u8 SCL_channel );//从AS5600读取一次数据
u32 Get_Ini_Val(u8 SDA_channel,u8 SCL_channel ); //得到上电后角度初始值
void Get_Temp_Add(void); //等到角度增量(原始值表示的)
void Change_angle(void); //将原始增量数据转化为角度
void Get_Num_sign(void); //用于过零点计数


#endif


评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

睿智の男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值