IIC的初始化函数整体部分,最初是想硬件IIC配合DMA读取的,但是莫名在读取的时候角度值变化了一两下,就直接读取不了了,卡在了IIC的start里,最后还是直接使用的硬件IIC直接读取,没有使用DMA了,里面屏蔽的部分就是使用DMA的部分,有想法的可以自己试试
#include "myiic.h"
#include "delay.h"
#include <stdio.h> // 可选,用于调试输出
/* 全局变量定义 */
//volatile uint8_t i2c_rx_buf[2]; // 接收缓冲区(高字节+低字节)
//volatile uint8_t i2c_dma_done = 0; // DMA传输完成标志
/**
* @brief I2C1 DMA模式初始化
*/
void I2C1_DMA_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
I2C_InitTypeDef I2C_InitStruct = {0};
// DMA_InitTypeDef DMA_InitStruct = {0};
// NVIC_InitTypeDef NVIC_InitStruct = {0};
/* 1. 时钟使能 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB /*| RCC_AHB1Periph_DMA1*/, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
/* 2. GPIO配置:PB6(SCL), PB7(SDA) */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; // 开漏输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 复用功能映射 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);
// /* 4. DMA配置(I2C1_RX使用DMA1 Stream0) */
// DMA_InitStruct.DMA_Channel = DMA_Channel_1; // I2C1_RX对应Channel1
// DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;
// DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)i2c_rx_buf;
// DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; // 外设→内存
// DMA_InitStruct.DMA_BufferSize = 2; // 接收2字节
// DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增
// DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
// DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; // 单次传输模式
// DMA_InitStruct.DMA_Priority = DMA_Priority_High;
// DMA_Init(DMA1_Stream0, &DMA_InitStruct);
// /* 5. 中断配置 */
// NVIC_InitStruct.NVIC_IRQChannel = DMA1_Stream0_IRQn;
// NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
// NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
// NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
// NVIC_Init(&NVIC_InitStruct);
// DMA_ITConfig(DMA1_Stream0, DMA_IT_TC, ENABLE); // 使能传输完成中断
/* 3. I2C参数配置(400kHz快速模式) */
I2C_InitStruct.I2C_ClockSpeed = 400000;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 主机模式无需地址
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C1, &I2C_InitStruct);
/* 6. 启动外设 */
// 此处不启动DMA通道,等待每次传输时在AS5600_ReadAngle中启动
// DMA_Cmd(DMA1_Stream0, ENABLE);
I2C_Cmd(I2C1, ENABLE);
}
/**
* @brief DMA传输完成中断服务函数
*/
//void DMA1_Stream0_IRQHandler(void) {
// if (DMA_GetITStatus(DMA1_Stream0, DMA_IT_TCIF0)) {
// // 传输完成后生成STOP信号
// I2C_GenerateSTOP(I2C1, ENABLE);
// i2c_dma_done = 1; // 设置传输完成标志
// DMA_ClearITPendingBit(DMA1_Stream0, DMA_IT_TCIF0); // 清除中断标志
// }
//}
/**
* @brief 读取AS5600角度值
*/
//float AS5600_ReadAngle(void) {
// uint8_t reg_addr = 0x0C; // 角度高字节寄存器地址
// /* 1. 发送寄存器地址(写模式) */
// I2C_GenerateSTART(I2C1, ENABLE);
// while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待START完成
//
// I2C_Send7bitAddress(I2C1, AS5600_I2C_ADDR, I2C_Direction_Transmitter);
// while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
//
// I2C_SendData(I2C1, reg_addr); // 发送要读取的寄存器地址
// while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// /* 2. 重启I2C总线(读模式) */
// I2C_GenerateSTART(I2C1, ENABLE);
// while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
//
// I2C_Send7bitAddress(I2C1, AS5600_I2C_ADDR, I2C_Direction_Receiver);
// while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
// /* 3. DMA传输前复位DMA */
// DMA_Cmd(DMA1_Stream0, DISABLE); // 禁用DMA
// DMA_SetCurrDataCounter(DMA1_Stream0, 2); // 重置传输计数器为2字节
// DMA_ClearFlag(DMA1_Stream0, DMA_FLAG_TCIF0); // 清除传输完成标志
//
// /* 4. 启动DMA传输 */
// i2c_dma_done = 0; // 清除完成标志
// DMA_Cmd(DMA1_Stream0, ENABLE); // 重新启动DMA
// I2C_DMACmd(I2C1, ENABLE); // 允许I2C触发DMA请求
//
//
//
// /* 5. 等待传输完成(建议添加超时机制) */
// while (!i2c_dma_done);
// i2c_dma_done = 0; // 清除标志位
// /* 6. 关闭I2C DMA请求 */
// I2C_DMACmd(I2C1, DISABLE);
// /* 7. 生成STOP信号 */
// // I2C_GenerateSTOP(I2C1, ENABLE);
// /* 6. 计算角度(12位分辨率) */
// uint16_t raw_angle = (i2c_rx_buf[0] << 8) | i2c_rx_buf[1];
// return (raw_angle * 360.0f) / 4096.0f; // 转换为0~360°
//}
float AS5600_ReadAngle_Polling(void) {
uint8_t hi, lo;
uint16_t raw;
/* —— 1. 先写寄存器地址 —— */
I2C_GenerateSTART(I2C1, ENABLE);
printf("step 1");
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, AS5600_I2C_ADDR, I2C_Direction_Transmitter);
printf("step 2");
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, 0x0C); // 角度高字节寄存器
printf("step 3");
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
/* —— 2. 重启总线,进入读模式 —— */
I2C_GenerateSTART(I2C1, ENABLE);
printf("step 4");
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, AS5600_I2C_ADDR, I2C_Direction_Receiver);
printf("step 5");
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
/* —— 新增:设置 POS —— */
I2C1->CR1 |= I2C_CR1_POS; // 让 ACK/NACK 针对下一字节位置生效
I2C_AcknowledgeConfig(I2C1, ENABLE); // 确保 ACK 在清 ADDR 前是有效的
/* —— 3. 精确两字节接收 ——
按照参考手册,第 483 页,两字节接收的正确顺序是:
1) 收到 ADDR,清标志
2) 关闭 ACK,设置 POS(可省略)
3) 等待 BTF
4) 生成 STOP
5) 读 DR 两次 —— :contentReference[oaicite:0]{index=0} */
// 清除 ADDR 标志(先读 SR1 再读 SR2)
(void)I2C1->SR1;
(void)I2C1->SR2;
// 关闭 ACK,让最后一个字节 NACK
I2C_AcknowledgeConfig(I2C1, DISABLE);
// 等待 BTF:此时 DR 已有第1字节,移位寄存器有第2字节
printf("step 6");
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_BTF));
// 生成 STOP,释放总线
I2C_GenerateSTOP(I2C1, ENABLE);
// 依次读高、低字节
hi = I2C_ReceiveData(I2C1);
lo = I2C_ReceiveData(I2C1);
// 恢复 ACK,供下次读取使用
I2C_AcknowledgeConfig(I2C1, ENABLE);
raw = ((uint16_t)hi << 8) | lo;
return raw * 360.0f / 4096.0f;
}
读取AS5600的函数,在测试的时候卡在了step 6那里,找不到原因,直接问的AI,把新增的那部分加上后就很正常的使用了
/* —— 新增:设置 POS —— */
I2C1->CR1 |= I2C_CR1_POS; // 让 ACK/NACK 针对下一字节位置生效
I2C_AcknowledgeConfig(I2C1, ENABLE); // 确保 ACK 在清 ADDR 前是有效的
具体怎么理解,我没深入,反正能用了就是了,下面是AI的解释:
设置 POS (I2C_CR1_POS):让 ACK/NACK 针对“第二个字节”生效,缺它 BTF 往往不会被置位。
清 ADDR 前保持 ACK:保证地址阶段 ACK 正确。
清 ADDR 后再关 ACK:生成对第二字节的 NACK。
等待 BTF → 发 STOP → 读两字节。
最后清除 POS、恢复 ACK,为下一次读取做准备。
IIC的头文件
#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
/* 定义AS5600设备地址 */
#define AS5600_I2C_ADDR 0x36 << 1 // 7位地址左移1位
/* 全局缓冲区声明(供外部访问) */
//extern volatile uint8_t i2c_rx_buf[2];
//extern volatile uint8_t i2c_dma_done;
/**
* @brief I2C1 DMA模式初始化
* @note 配置GPIO、I2C外设和DMA传输
* @retval None
*/
void I2C1_DMA_Init(void);
/**
* @brief 从AS5600读取角度值
* @note 通过I2C+DMA读取原始数据并转换为角度
* @retval 角度值(0~360度)
*/
//float AS5600_ReadAngle(void);
float AS5600_ReadAngle_Polling(void);
#endif
main.c里的调用,里面有之前屏蔽2804电机开环驱动的代码,把这个读取角度值合并后续做其他测试
#include "stm32f4xx.h"
#include <math.h>
#include "usart.h"
#include "sys.h"
#include "delay.h"
#include "myiic.h"
//#include "pwm.h"
// PI 常量
//#define PI 3.14159265358979323846f
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
printf("UART OK\n");
/* 初始化I2C DMA模块 */
I2C1_DMA_Init();
printf("I2C inited\n");
float angle;
// PWM_Init();
// EnableMotor(1);
// 角度变量(以弧度计),初始值为 0
// float angle = 0.0f;
// 每次循环增加的角度,决定转速。此处示例每次增加 0.05 弧度(可根据实际需求调整)
//float deltaAngle = 6.28f;
// 三相角偏移(120度 = 2π/3 弧度)
// float phaseOffset = 2 * PI / 3.0f;
// 最大 PWM 脉冲宽度范围(0~PWM_PERIOD)
//uint16_t maxDuty = PWM_PERIOD;
while (1)
{
// 计算每相的占空比:
// 为了得到 0~maxDuty 之间的值,先将 sin 结果(范围[-1,1])转换为[0,1],再乘以 maxDuty
// float sinA = (sinf(angle) + 1.0f) / 2.0f;
// float sinB = (sinf(angle - phaseOffset) + 1.0f) / 2.0f;
// float sinC = (sinf(angle - 2 * phaseOffset) + 1.0f) / 2.0f;
// uint16_t dutyA = (uint16_t)(sinA * maxDuty);
// uint16_t dutyB = (uint16_t)(sinB * maxDuty);
// uint16_t dutyC = (uint16_t)(sinC * maxDuty);
// 设置 PWM 输出
// SetMotorSpeed(dutyA, dutyB, dutyC);
// // 累加角度,保证 0 到 2π 循环
// angle += deltaAngle;
// if(angle > 2 * PI *1000 ){
// angle -= 2 * PI *1000;
// printf("angle: %0.1f",angle);
//
// }
/* 读取角度值 */
angle = AS5600_ReadAngle_Polling();
/* 示例:通过串口输出角度值(需实现printf重定向) */
printf("Current Angle: %.2f°\n", angle);
// 延时以控制更新速率(这里延时数值可调整)
delay_ms(100);
}
}
最终结果显现如图: