STM32/STM8技术论坛
直播中

谢凡硕

未满1年用户 7经验值
擅长:嵌入式技术
私信 关注
[问答]

stm32f103c8t6用中断采出来的数来回跳?怎么样才能稳定下来?

STM32F103C8T6用中断采集AD677的16位数字信号,采的是2V基准电压,采出的值在0.5和3之间来回跳。
AD677的参考电压是5V,AD677采样时序如下屏幕截图 2025-04-28 165610.png
串口助手得到的数如下,我用fb判断busy是否为高,用fa判断busy是否为低,所以fb和fa之间的数就是采出来的数
屏幕截图 2025-04-28 170557.png

我用外部中断检测SCLK的上升沿,检测到上升沿就进入中断,把sdata的值赋给data,采完16位通过usart发到上位机,以下是代码

#define SAMPLE_HIGH()     GPIO_SetBits(GPIOA, GPIO_Pin_4)
#define SAMPLE_LOW()      GPIO_ResetBits(GPIOA, GPIO_Pin_4)
#define CLK_HIGH()        GPIO_SetBits(GPIOA, GPIO_Pin_6)
#define CLK_LOW()         GPIO_ResetBits(GPIOA, GPIO_Pin_6)
#define CAL_HIGH()        GPIO_SetBits(GPIOA, GPIO_Pin_7)
#define CAL_LOW()         GPIO_ResetBits(GPIOA, GPIO_Pin_7)

#define READ_SDATA()      GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8)
#define READ_BUSY()       GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)
#define READ_SCLK()       GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)
#define READ_CLK()        GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_6)

#define ResetCOUNTER()    TIM_SetCounter(TIM3, 0)

volatile uint16_t data = 0;             // 最终的16位数据
volatile uint8_t bitCounter = 0;        // 位计数器
volatile uint8_t dataReady = 0;         // 数据就绪标志

void GPIO_ENABLE(uint32_t RCC_APB2Periph, GPIO_TypeDef* GPIOx, GPIOMode_TypeDef GPIO_Mode, uint16_t GPIO_Pin, GPIOSpeed_TypeDef GPIO_Speed)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed;
	GPIO_Init(GPIOx, &GPIO_InitStructure);
}

void AD_Init(void)
{
	GPIO_ENABLE(RCC_APB2Periph_GPIOA, GPIOA, GPIO_Mode_AF_PP, GPIO_Pin_6, GPIO_Speed_50MHz);
	GPIO_ENABLE(RCC_APB2Periph_GPIOA, GPIOA, GPIO_Mode_Out_PP, GPIO_Pin_4 | GPIO_Pin_7, GPIO_Speed_50MHz);
	GPIO_ENABLE(RCC_APB2Periph_GPIOA, GPIOA, GPIO_Mode_IN_FLOATING, GPIO_Pin_8, GPIO_Speed_50MHz);
	GPIO_ENABLE(RCC_APB2Periph_GPIOB, GPIOB, GPIO_Mode_IN_FLOATING, GPIO_Pin_10 | GPIO_Pin_11, GPIO_Speed_50MHz);
	
	//A6、tim3ch1初始化,输出pwm波
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	TIM_InternalClockConfig(TIM3);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period = 1000 - 1;   //ARR
	TIM_TimeBaseInitStruct.TIM_Prescaler = 4 - 1; //PSC,18kHz
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	TIM_OCInitTypeDef TIM_OCInitStruct;
	TIM_OCStructInit(&TIM_OCInitStruct);
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = 500;  //CCR
	
	TIM_OC1Init(TIM3, &TIM_OCInitStruct);
	
//	TIM_Cmd(TIM3, ENABLE);
}
void PB10EXTI_Init(void)
{
	//B10、EXTI
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
 
	//设置IO口与中断线的映射关系
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource10);
	
	//初始化线上中断
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line10;
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStruct);
	
	//配置中断分组
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_InitStruct);
}

void EXTI15_10_IRQHandler(void)
{
	int8_t temp;
//    static uint32_t lastCaptureTime = 0;
//    uint32_t currentCaptureTime;
    
    if(EXTI_GetITStatus(EXTI_Line10) != RESET) 
    {
        EXTI_ClearITPendingBit(EXTI_Line10);//重置标志位
        
//        currentCaptureTime = TIM_GetCapture1(TIM1);
//        for(volatile int i = 0; i < 25; i++);
        // 接收一位数据
        if(bitCounter < 16) 
		{   
            // 将读取的位存入capturedValue
            data = (data << 1) | READ_SDATA();
            bitCounter++;
            
            // 当收到16位数据时设置数据就绪标志
            if(bitCounter == 16) 
			{
                dataReady = 1;
                bitCounter = 0;  // 重置位计数器,准备下一次接收
					if(dataReady == 1)
					{
						temp = (data >> 8) & 0xFF;  //高8位
						Serial_SendByte(temp);
		
						temp = (data & 0xFF);  //低8位
						Serial_SendByte(temp);
	
//						Serial_SendByte(0xF7);//发送完成
		
						dataReady = 0;//重置标志位
					}
			}
        }
//        lastCaptureTime = currentCaptureTime;

    }
}
已退回27积分

回帖(2)

chenwei6991627

2025-4-29 08:44:38
外部输出对不对呢
3 举报

余温重顾

2025-5-7 17:57:24

针对AD677数据采集不稳定的问题,以下是分步解决方案:




1. 确认硬件稳定性



  • 电源与接地  

    • 检查AD677的5V参考电压是否纯净,用示波器观察是否有纹波(建议并联10μF+0.1μF电容滤波)。

    • 确保STM32与AD677共地,避免地弹噪声。


  • 信号完整性  

    • SCLK和SDATA信号线尽量短,远离高频干扰源,可串联33Ω电阻抑制振铃。

    • 若使用杜邦线连接,改用屏蔽双绞线。





2. 调整数据读取时机


AD677数据在SCLK的下降沿稳定,因此应改为在SCLK的下降沿触发中断读取数据:


// 修改外部中断触发边沿为下降沿
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;



3. 优化中断服务函数



  • 确保数据位对齐

    按16位长度循环读取,每次中断读取1位,按顺序拼接数据:

    volatile uint16_t adc_data = 0;
    volatile uint8_t bit_counter = 0;


void EXTI_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_LineX) != RESET) {
// 在SCLK下降沿读取SDATA引脚状态
uint8_t bit = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_X);


    adc_data = (adc_data << 1) | bit; // 高位在前
    bit_counter++;

    if (bit_counter >= 16) {
        // 完成16位采集,禁用中断或重置计数器
        EXTI_IRQDisable();
        bit_counter = 0;

        // 发送数据(需添加校验或滤波)
        USART_SendData(adc_data);
    }
    EXTI_ClearITPendingBit(EXTI_LineX);
}

}



---

### **4. 添加软件滤波**
在STM32端对连续采样值做滑动平均滤波:
```c
#define FILTER_SIZE 8
uint16_t filter_buffer[FILTER_SIZE];
uint8_t filter_index = 0;

void apply_filter(uint16_t raw_data) {
    filter_buffer[filter_index] = raw_data;
    filter_index = (filter_index + 1) % FILTER_SIZE;

    uint32_t sum = 0;
    for (uint8_t i = 0; i < FILTER_SIZE; i++) {
        sum += filter_buffer[i];
    }
    uint16_t avg = sum / FILTER_SIZE;

    USART_SendData(avg); // 发送滤波后的数据
}



5. 检查时序与BUSY信号



  • 同步BUSY信号

    在AD677转换期间(BUSY为高),暂停数据采集。示例代码:
    while (GPIO_ReadInputDataBit(GPIOA, BUSY_PIN) == HIGH); // 等待BUSY变低
    EXTI_IRQEnable(); // 开始采集




6. 验证参考电压与数据范围



  • AD677的参考电压为5V时,输出范围应为0~5V。若输入是2V基准,理论输出值为:
    [
    text{数字量} = frac{2V}{5V} times 65535 = 26214.4 quad (0x6666)
    ]
    检查实际接收值是否接近该理论值,若偏差大则需校准。




7. 降低时钟频率测试


在AD677初始化时降低SCLK频率,排除因速度过快导致的数据丢失:


// 调整GPIO模拟SPI的延时(示例)
void delay_us(uint32_t us) {
    // 根据STM32主频实现微秒级延时
}

void generate_sclk() {
    CLK_HIGH();
    delay_us(10); // 降低SCLK频率至约50kHz
    CLK_LOW();
    delay_us(10);
}



8. 使用硬件SPI(推荐)


若STM32的SPI资源可用,改用硬件SPI自动读取数据,避免中断延迟:


// 配置SPI为主机模式,CPOL=0, CPHA=1(下降沿采样)
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_Init(SPI1, &SPI_InitStructure);

// 读取数据
uint16_t data = SPI_ReceiveData(SPI1);



总结:优先检查硬件连接与时序,调整中断触发边沿,添加滤波,并考虑切换为硬件SPI。通过上述步骤,数据稳定性应显著提升。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分