基于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