【基于正点原子的探索者STM32F407使用硬件IIC读取AS5600磁编码器角度值】

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);
    }
}

最终结果显现如图:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值