简单理解:基于 STM32 的 AS5600 角度读取(模拟 I2C 实现

📅 发布时间:2026/7/10 8:20:21 👁️ 浏览次数:
简单理解:基于 STM32 的 AS5600 角度读取(模拟 I2C 实现
/************************************************ 读取AS5600的角度(模拟I2C) PB10--SCL PB11--SDA 本程序仅供学习引用代码请标明出处 使用教程https://blog.csdn.net/loop222/article/details/120431638 《SimpleFOC移植STM32三—— 角度读取》 创建日期20210909 作 者loop222 郑州 ************************************************/ /******************************************************************************/ #define RAW_Angle_Hi 0x0C // AS5600原始角度值高位寄存器地址 #define RAW_Angle_Lo 0x0D // AS5600原始角度值低位寄存器地址 #define AS5600_Address 0x36 // AS5600的I2C设备地址 /***************************************************************************/ #define SDA_IN() {GPIOB-CRH0xFFFF0FFF;GPIOB-CRH|0x00008000;} // 配置PB11为输入模式 #define SDA_OUT() {GPIOB-CRH0xFFFF0FFF;GPIOB-CRH|0x00003000;} // 配置PB11为输出模式 #define READ_SDA (GPIOB-IDR(111)) // 读取PB11引脚电平状态 #define IIC_SCL_1 GPIO_SetBits(GPIOB,GPIO_Pin_10) // 设置PB10(SCL)为高电平 #define IIC_SCL_0 GPIO_ResetBits(GPIOB,GPIO_Pin_10) // 设置PB10(SCL)为低电平 #define IIC_SDA_1 GPIO_SetBits(GPIOB,GPIO_Pin_11) // 设置PB11(SDA)为高电平 #define IIC_SDA_0 GPIO_ResetBits(GPIOB,GPIO_Pin_11) // 设置PB11(SDA)为低电平 /***************************************************************************/ void delay_s(u32 i); // 延时函数声明需要用户自行实现具体延时逻辑 /***************************************************************************/ void IIC_Start(void) // I2C起始信号函数 { IIC_SDA_1; // SDA先置高 IIC_SCL_1; // SCL置高 delay_s(20); // 延时保证时序稳定 IIC_SDA_0; // SDA拉低SCL高时SDA下降沿为起始信号 delay_s(20); // 延时保证时序稳定 IIC_SCL_0; // SCL拉低准备发送数据 } /***************************************************************************/ void IIC_Stop(void) // I2C停止信号函数 { IIC_SCL_0; // SCL先置低 IIC_SDA_0; // SDA置低 delay_s(20); // 延时保证时序稳定 IIC_SCL_1; // SCL拉高 delay_s(20); // 延时保证时序稳定 IIC_SDA_1; // SDA拉高SCL高时SDA上升沿为停止信号 delay_s(20); // 延时保证时序稳定 } /***************************************************************************/ //1-fail,0-success u8 IIC_Wait_Ack(void) // 等待从机应答函数返回1表示应答失败0表示成功 { u8 ucErrTime0; // 应答超时计数变量初始化 SDA_IN(); // 切换SDA为输入模式读取从机应答 IIC_SDA_1; // 释放SDA总线由从机拉低表示应答 IIC_SCL_1; // SCL拉高等待从机应答 delay_s(10); // 延时等待应答 while(READ_SDA!0) // 检测SDA是否被从机拉低应答 { if(ucErrTime250) // 超时计数增加判断是否超时 { SDA_OUT(); // 超时后切换SDA为输出模式 IIC_Stop(); // 发送停止信号 return 1; // 返回1表示应答失败 } } SDA_OUT(); // 应答成功切换SDA为输出模式 IIC_SCL_0; // SCL拉低准备后续操作 return 0; // 返回0表示应答成功 } /***************************************************************************/ void IIC_Ack(void) // 主机发送应答信号ACK { IIC_SCL_0; // SCL先置低 IIC_SDA_0; // SDA拉低表示应答 delay_s(20); // 延时保证时序稳定 IIC_SCL_1; // SCL拉高让从机检测应答 delay_s(20); // 延时保证时序稳定 IIC_SCL_0; // SCL拉低结束应答 } /***************************************************************************/ void IIC_NAck(void) // 主机发送非应答信号NACK { IIC_SCL_0; // SCL先置低 IIC_SDA_1; // SDA拉高表示非应答 delay_s(20); // 延时保证时序稳定 IIC_SCL_1; // SCL拉高让从机检测非应答 delay_s(20); // 延时保证时序稳定 IIC_SCL_0; // SCL拉低结束非应答 } /***************************************************************************/ void IIC_Send_Byte(u8 txd) // I2C发送一个字节数据函数 { u32 i; // 循环变量 IIC_SCL_0; // SCL先置低准备发送 for(i0;i8;i) // 循环8次发送8位数据高位先行 { if((txd0x80)!0)IIC_SDA_1; // 判断最高位是否为1是则SDA置高 else IIC_SDA_0; // 否则SDA置低 txd1; // 数据左移一位准备发送下一位 delay_s(20); // 延时保证时序稳定 IIC_SCL_1; // SCL拉高让从机读取当前位数据 delay_s(20); // 延时保证时序稳定 IIC_SCL_0; // SCL拉低准备发送下一位 delay_s(20); // 延时保证时序稳定 } } /***************************************************************************/ u8 IIC_Read_Byte(u8 ack) // I2C读取一个字节数据函数ack1发送ACK0发送NACK { u8 i,rcv0; // 循环变量和接收数据变量初始化 SDA_IN(); // 切换SDA为输入模式准备读取数据 for(i0;i8;i) // 循环8次读取8位数据高位先行 { IIC_SCL_0; // SCL先置低 delay_s(20); // 延时保证时序稳定 IIC_SCL_1; // SCL拉高读取当前位数据 rcv1; // 接收数据左移一位准备存下一位 if(READ_SDA!0)rcv; // 读取SDA电平高电平则接收数据加1 delay_s(10); // 延时保证时序稳定 } SDA_OUT(); // 读取完成切换SDA为输出模式 if(!ack)IIC_NAck(); // 如果ack为0发送非应答信号 else IIC_Ack(); // 否则发送应答信号 return rcv; // 返回读取到的字节数据 } /***************************************************************************/ u8 AS5600_ReadOneByte(u8 addr) // 读取AS5600指定寄存器的一个字节数据 { u8 temp; // 存储读取到的数据 IIC_Start(); // 发送I2C起始信号 IIC_Send_Byte(AS5600_Address1); // 发送设备地址写位最低位0表示写 IIC_Wait_Ack(); // 等待从机应答 IIC_Send_Byte(addr); // 发送要读取的寄存器地址 IIC_Wait_Ack(); // 等待从机应答 IIC_Start(); // 发送重复起始信号重启I2C IIC_Send_Byte((AS5600_Address1)1); // 发送设备地址读位最低位1表示读 IIC_Wait_Ack(); // 等待从机应答 tempIIC_Read_Byte(0); // 读取一个字节发送非应答结束读取 IIC_Stop(); // 发送停止信号 return temp; // 返回读取到的寄存器数据 } /***************************************************************************/ u16 AS5600_ReadRawAngleTwo(void) // 读取AS5600原始角度值16位 { u8 dh,dl; // 存储角度值高位和低位字节 IIC_Start(); // 发送I2C起始信号 IIC_Send_Byte(AS5600_Address1); // 发送设备地址写位 IIC_Wait_Ack(); // 等待从机应答 IIC_Send_Byte(RAW_Angle_Hi); // 发送角度值高位寄存器地址 IIC_Wait_Ack(); // 等待从机应答 IIC_Start(); // 发送重复起始信号 IIC_Send_Byte((AS5600_Address1)1); // 发送设备地址读位 IIC_Wait_Ack(); // 等待从机应答 dhIIC_Read_Byte(1); // 读取高位字节发送应答表示还要读下一个字节 dlIIC_Read_Byte(0); // 读取低位字节发送非应答结束读取 IIC_Stop(); // 发送停止信号 return ((dh8)dl); // 组合高低位返回16位原始角度值 } /***************************************************************************/总结核心功能这段代码通过 STM32 模拟 I2C 总线协议实现对 AS5600 角度传感器的单字节寄存器读取和 16 位原始角度值读取关键是严格遵循 I2C 的起始、停止、应答、数据收发时序。关键宏定义SDA_IN()/SDA_OUT()用于切换 PB11 的输入输出模式IIC_SCL_1/0/IIC_SDA_1/0用于控制 I2C 总线电平是模拟 I2C 的基础。读取逻辑读取 16 位角度值时先发送高位寄存器地址再通过重复起始信号切换为读模式先读高位再读低位最后组合成完整的 16 位数据。注意代码中声明的delay_s(u32 i)函数需要你根据实际硬件时钟频率实现具体的延时逻辑确保 I2C 时序符合 AS5600 的要求。/***************************************************************************/ //arr自动重装值。 psc时钟预分频数 //定时器溢出时间计算方法:Tout((arr1)*(psc1))/Ft us. //Ft定时器工作频率,单位:Mhz void TIM2_Init(u16 arr,u16 psc) // TIM2定时器初始化函数arr自动重装值psc预分频值 { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; // 定义定时器时基初始化结构体 NVIC_InitTypeDef NVIC_InitStructure; // 定义中断优先级配置结构体 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); // 使能TIM2的时钟TIM2挂载在APB1总线 NVIC_InitStructure.NVIC_IRQChannelTIM2_IRQn; // 指定中断通道为TIM2全局中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority0; // 设置抢占优先级为0最高 NVIC_InitStructure.NVIC_IRQChannelSubPriority0; // 设置响应优先级为0最高 NVIC_InitStructure.NVIC_IRQChannelCmdENABLE; // 使能该中断通道 NVIC_Init(NVIC_InitStructure); // 根据配置初始化NVIC中断控制器 TIM_TimeBaseInitStructure.TIM_Period arr; // 设置定时器自动重装值 TIM_TimeBaseInitStructure.TIM_Prescalerpsc; // 设置定时器预分频系数 TIM_TimeBaseInitStructure.TIM_CounterModeTIM_CounterMode_Up; // 设置计数模式为向上计数 TIM_TimeBaseInitStructure.TIM_ClockDivisionTIM_CKD_DIV1; // 设置时钟分频因子为1不分频 TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure); // 根据配置初始化TIM2时基参数 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 使能TIM2的更新中断溢出中断 TIM_Cmd(TIM2,ENABLE); // 使能TIM2定时器开始计数 } /***************************************************************************/ //定时器2中断服务函数 void TIM2_IRQHandler(void) // TIM2中断服务函数中断发生时自动执行 { if(TIM_GetITStatus(TIM2,TIM_IT_Update)SET) // 检查是否是TIM2更新溢出中断 { time1_cntr; // 全局变量time1_cntr自增用于计时/计数 time2_cntr; // 全局变量time2_cntr自增用于计时/计数 } TIM_ClearITPendingBit(TIM2,TIM_IT_Update); // 清除TIM2更新中断标志位避免重复进入中断 } /***************************************************************************/总结核心功能这段代码完成 STM32 TIM2 定时器的初始化配置含中断优先级并实现定时器溢出中断服务函数通过自增全局变量实现计时 / 计数功能。关键配置定时器采用向上计数模式时钟不分频抢占 / 响应优先级均设为最高使能了更新溢出中断。中断处理中断服务函数中先判断中断类型确认是溢出中断后更新计数变量最后必须清除中断标志位防止重复触发。补充说明代码中用到的time1_cntr和time2_cntr需要在全局作用域声明如u32 time1_cntr0; u32 time2_cntr0;否则会编译报错。