硬汉嵌入式论坛

 找回密码
 立即注册
查看: 286|回复: 9
收起左侧

[SPI/QSPI] SPI主机同步读取两个SPI从机数据,从机数据来自于ADC采样后中断处理的数据,读取后的数据有多个重复点

[复制链接]

4

主题

20

回帖

32

积分

新手上路

积分
32
发表于 2025-11-5 14:49:55 | 显示全部楼层 |阅读模式


功能需求:希望能够在尽量高的ADC采样频率下,通过SPI主机读取同步两个SPI从机的数据,每组数据不重复,我需要怎么配置和修改处理逻辑

问题:SPI主机同步读取两个SPI从机数据,从机数据来自于ADC采样处理后数据,读取后的数据有多个重复点。之前以为是ADC采样频率过低,但是提升采样率以后重复点数据依旧保持不变。

希望可以看一下配置是否存在问题,或者处理是否符合逻辑,最下面部分是我尝试的三个方案,但是效果都不好。

/***************************************************************SPI主机部分的配置和逻辑**************************************************************************/
主机SPI配置:

void SPI4_Config_Init(void)
{
        hspi4.Instance                                                                 = SPI4;       
        hspi4.Init.Mode                                                         = SPI_MODE_MASTER;     
        hspi4.Init.Direction                                   = SPI_DIRECTION_2LINES;      
        hspi4.Init.DataSize                                         = SPI_DATASIZE_8BIT;     
        hspi4.Init.CLKPolarity                                 = SPI_POLARITY_LOW;                        
        hspi4.Init.CLKPhase                                         = SPI_PHASE_1EDGE;                                       
        hspi4.Init.NSS                                                                 = SPI_NSS_HARD_OUTPUT;                                         
       

        hspi4.Init.BaudRatePrescaler                             = SPI_BAUDRATEPRESCALER_2;

        hspi4.Init.FirstBit                                                               = SPI_FIRSTBIT_MSB;                       
        hspi4.Init.TIMode                                                               = SPI_TIMODE_DISABLE;                
        hspi4.Init.CRCCalculation                                            = SPI_CRCCALCULATION_DISABLE;        
        hspi4.Init.CRCPolynomial                                             = 0x0;                                                               
        hspi4.Init.NSSPMode                                                       = SPI_NSS_PULSE_DISABLE;                                           
        hspi4.Init.NSSPolarity                                                     = SPI_NSS_POLARITY_LOW;                                     
        hspi4.Init.FifoThreshold                                             = SPI_FIFO_THRESHOLD_01DATA;                         
        hspi4.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;   
        hspi4.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;          
        hspi4.Init.MasterSSIdleness                                   = SPI_MASTER_SS_IDLENESS_01CYCLE;      
        hspi4.Init.MasterInterDataIdleness                 = SPI_MASTER_INTERDATA_IDLENESS_01CYCLE;   
        hspi4.Init.MasterReceiverAutoSusp                 = SPI_MASTER_RX_AUTOSUSP_DISABLE;         
        hspi4.Init.MasterKeepIOState                                   = SPI_MASTER_KEEP_IO_STATE_ENABLE;          
        hspi4.Init.IOSwap                                                               = SPI_IO_SWAP_DISABLE;               

        HAL_SPI_Init(&hspi4);
}


void SPI6_Config_Init(void)
{
        hspi6.Instance                                                                 = SPI6;                                       
        hspi6.Init.Mode                                                         = SPI_MODE_MASTER;                   
        hspi6.Init.Direction                                   = SPI_DIRECTION_2LINES;              
        hspi6.Init.DataSize                                         = SPI_DATASIZE_8BIT;                      
        hspi6.Init.CLKPolarity                                 = SPI_POLARITY_LOW;                        
        hspi6.Init.CLKPhase                                         = SPI_PHASE_1EDGE;                               
        hspi6.Init.NSS                                                                 = SPI_NSS_HARD_OUTPUT;                                 
       

        hspi6.Init.BaudRatePrescaler                             = SPI_BAUDRATEPRESCALER_2;

        hspi6.Init.FirstBit                                                               = SPI_FIRSTBIT_MSB;                                       
        hspi6.Init.TIMode                                                               = SPI_TIMODE_DISABLE;                
        hspi6.Init.CRCCalculation                                            = SPI_CRCCALCULATION_DISABLE;                
        hspi6.Init.CRCPolynomial                                             = 0x0;                                                       
        hspi6.Init.NSSPMode                                                       = SPI_NSS_PULSE_DISABLE;                           
        hspi6.Init.NSSPolarity                                                     = SPI_NSS_POLARITY_LOW;                             
        hspi6.Init.FifoThreshold                                             = SPI_FIFO_THRESHOLD_01DATA;         
        hspi6.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;   
        hspi6.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;   
        hspi6.Init.MasterSSIdleness                                   = SPI_MASTER_SS_IDLENESS_01CYCLE;         
        hspi6.Init.MasterInterDataIdleness                 = SPI_MASTER_INTERDATA_IDLENESS_01CYCLE;           
        hspi6.Init.MasterReceiverAutoSusp                 = SPI_MASTER_RX_AUTOSUSP_DISABLE;        
        hspi6.Init.MasterKeepIOState                                   = SPI_MASTER_KEEP_IO_STATE_ENABLE;              
        hspi6.Init.IOSwap                                                               = SPI_IO_SWAP_DISABLE;                    

        HAL_SPI_Init(&hspi6);
}


主函数中的逻辑:
通过把数据读到以后写入缓冲区,缓冲区满了以后再统一做输出

        while (1)
        {
               
                wIdx = Get_FifoBuff_Widx(&g_fifoBuff);
                if(RINGBUFF_LEN == wIdx && 0 == readFlag)
                {
                        readFlag = 1;
                       
                        HAL_Delay(8 * 1000);
                }
               
                if(0 == readFlag)
                {
                        ret = HAL_SPI_TransmitReceive(&hspi4, tnsSPI4Buff, rcvSPI4Buff, TRANS_DATA_LEN, 0x1);
                        ret = HAL_SPI_TransmitReceive(&hspi6, tnsSPI6Buff, rcvSPI6Buff, TRANS_DATA_LEN, 0x1);
                        if(HAL_OK == ret)
                        {
                                if(0x81 == rcvSPI4Buff[0] && 0x81 == rcvSPI6Buff[0])
                                {
                                        checksum4_b8 = rcvSPI4Buff[0] + rcvSPI4Buff[1] + rcvSPI4Buff[2] + rcvSPI4Buff[3];
                                        checksum6_b8 = rcvSPI6Buff[0] + rcvSPI6Buff[1] + rcvSPI6Buff[2] + rcvSPI6Buff[3];

                                        if(checksum4_b8 == rcvSPI4Buff[TRANS_DATA_LEN - 1] && checksum6_b8 == rcvSPI6Buff[TRANS_DATA_LEN - 1])
                                        {
                                                angleWrite[0] = rcvSPI4Buff[1] << 16 | rcvSPI4Buff[2] << 8 | rcvSPI4Buff[3];
                                                angleWrite[1] = rcvSPI6Buff[1] << 16 | rcvSPI6Buff[2] << 8 | rcvSPI6Buff[3];

                                                Write_fifoBuff(&g_fifoBuff, angleWrite, 2);
                                        }
                                }
                        }
                }
               
                if(1 == readFlag)
                {
                        rIdx = Get_FifoBuff_Ridx(&g_fifoBuff);
                        if(RINGBUFF_LEN == rIdx)
                        {
                                continue;
                        }
                       
                        Read_fifoBuff(&g_fifoBuff, angleRead, 2);
                        printf("%d,%d\r\n",angleRead[0],angleRead[1]);
                }
        }



/***************************************************************两个SPI从机部分的配置和逻辑**************************************************************************/

void SPI4_Config_Init(void)
{
        hspi4.Instance                                                                 = SPI4;               
        hspi4.Init.Mode                                                         = SPI_MODE_SLAVE;         
        hspi4.Init.Direction                                                 = SPI_DIRECTION_2LINES;              
        hspi4.Init.DataSize                                                 = SPI_DATASIZE_8BIT;                         
        hspi4.Init.CLKPolarity                                                 = SPI_POLARITY_LOW;                  
        hspi4.Init.CLKPhase                                                 = SPI_PHASE_1EDGE;                                   
        hspi4.Init.NSS                                                                 = SPI_NSS_HARD_INPUT;                                       
       

        hspi4.Init.BaudRatePrescaler                                 = SPI_BAUDRATEPRESCALER_2;

        hspi4.Init.FirstBit                                                         = SPI_FIRSTBIT_MSB;       
        hspi4.Init.TIMode                                                         = SPI_TIMODE_DISABLE;         
        hspi4.Init.CRCCalculation                                        = SPI_CRCCALCULATION_DISABLE;        
        hspi4.Init.CRCPolynomial                                         = 0x0;                                               
        hspi4.Init.NSSPMode                                                 = SPI_NSS_PULSE_DISABLE;                             
        hspi4.Init.NSSPolarity                                                 = SPI_NSS_POLARITY_LOW;                             
        hspi4.Init.FifoThreshold                                         = SPI_FIFO_THRESHOLD_02DATA;         
        hspi4.Init.TxCRCInitializationPattern                 = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;   
        hspi4.Init.RxCRCInitializationPattern                 = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;   
        hspi4.Init.IOSwap                                                         = SPI_IO_SWAP_DISABLE;       

        HAL_SPI_Init(&hspi4);
}


ADC中断的处理逻辑:

void DMA1_Stream1_IRQHandler(void)
{
       // 相关中断标识处理逻辑
      ....
       // dTempData 是通过对 ADC 采样的数据处理后的结果
      ....

       Write_SpiBuff(dTempData);
       bReady = 1;
}


主逻辑处理方法:

// 从机通过SPI发送 24bit 数据的帧格式
void TransmitData(uint32_t Data)
{
        uint32_t sum_check = 0;
        uint8_t SndByte[5] = {0};
        uint8_t byteCount = 0;

        uint8_t andFlag = 0xFF;
       
        // frame head
        SndByte[0] = 0x81;
        sum_check += SndByte[0];
        byteCount++;

        for(uint8_t i = 0; i < 3; i++)
        {
                // shift 0 <-> low, sheft 8 <-> middle, sheft 16 <-> high
                SndByte[byteCount] = (Data>> (2 - i) * 8) & andFlag;
               
                sum_check += SndByte[byteCount];
                byteCount++;
        }
        SndByte[byteCount] = sum_check & andFlag;

        HAL_SPI_Transmit(&hspi4, SndByte, 5, 0x1);
       
        return;
}


主循环:

        while(1)
        {
                        spiData = Read_SpiBuff();

                        TransmitData(spiData );
        }



读出的数据一直有重复点。



做过的尝试的方案,尝试的现象不太理解,希望如果理解的朋友给一些回复:
1、考虑是ADC采样后中断处理速率比较慢,SPI传输速率比较快,导致出现的重复点问题。当我不断的提升采样中断频率,读出的重复点数基本保持不变,这点就不是很理解;但是降低采样频率以后,重复点数增加,这里是正常的。
2、在ADC采样的DMA中断中增加处理完成标识 bReady ,在主函数中对标识判断和设置
在SPI从机中设置的逻辑为:

        while(1)
        {
                if(1 == bReady)
                {
                        spiData = Read_SpiAngBuff();

                        TransmitData(spiData );
                       
                        bReady = 0;
                }
        }

这样设置以后,在主机读取后的数据更加异常,还是存在重复点,或者直接丢失一段数据
3、考虑直接把传输数据部分放入到中断中,数据完全异常,感觉这个方案也不是很合理,但是不太肯定。


希望有遇到相关问题的朋友给一些建议,谢谢!
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
119430
QQ
发表于 2025-11-5 15:56:02 | 显示全部楼层

回帖奖励 +2 个金币

有个关键的地方没理解,楼主说的SPI从机是个什么设备,就是单纯的一个SPI接口ADC吗,还是另外一个MCU+ADC
回复

使用道具 举报

4

主题

20

回帖

32

积分

新手上路

积分
32
 楼主| 发表于 2025-11-5 17:08:33 | 显示全部楼层
eric2013 发表于 2025-11-5 15:56
有个关键的地方没理解,楼主说的SPI从机是个什么设备,就是单纯的一个SPI接口ADC吗,还是另外一个MCU+ADC

共有三块 MCU,都是 H7 的板子,一块 MCU 做 SPI 主机,两款 MCU 做 SPI 从机,其中那两款 MCU 从机通过内部 ADC 进行采样。
其中第一个问题找到原因了,因为 ADC 开启了过采样,使用的是 TIM 触发 ADC 采样,修改 TIM 参数所以 ADC 采样速率不变。
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
119430
QQ
发表于 2025-11-5 17:15:01 | 显示全部楼层
剑心v 发表于 2025-11-5 17:08
共有三块 MCU,都是 H7 的板子,一块 MCU 做 SPI 主机,两款 MCU 做 SPI 从机,其中那两款 MCU 从机通过 ...

这样的话,从机回复数据前先打印看下正常不,如果正常的话,那就是从机返回的数据有问题。
回复

使用道具 举报

4

主题

20

回帖

32

积分

新手上路

积分
32
 楼主| 发表于 2025-11-6 17:08:49 | 显示全部楼层
我做了测试,SPI 从机接收的数据一直都是正常的,在MCU中使用SPI主模式使用 bsp_DelayUS(100) 延时 100us 读取数据时,会有一两个点重复,结果是第一张图;在使用方式 2、(在ADC采样的DMA中断中增加处理完成标识 bReady ,在主函数中对标识判断和设置),读取的数据间断的就比较多,结果是第二张图. 这个结果考虑是中断占用时间比较多,在 main 函数中接收函数占用的时间比较少,所以出现缺失数据的情况。

总的来说,我想要解决的是: 希望读取的两个 SPI 从机的数据不重复(不然不能对两组同步的数据一一对应),我也尝试过使用 bsp_DelayUS() 函数,延迟时间从 100us 到 900 us 都会出现数据重复的情况,现在两个从机 MCU 采样的中断频率也调节到最高了,暂时没有想到好的办法实现  ———  

能够在尽量高的ADC采样频率下,通过SPI主机读取同步两个SPI从机的数据,每组数据不重复
图1.png
图2.png
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
119430
QQ
发表于 2025-11-7 09:37:39 | 显示全部楼层
剑心v 发表于 2025-11-6 17:08
我做了测试,SPI 从机接收的数据一直都是正常的,在MCU中使用SPI主模式使用 bsp_DelayUS(100) 延时 100us  ...

还是没太懂你的意思。

是SPI两个从机同时读取一段数据,然后SPI主机(是一个SPI接口接两个从机还是两个SPI接口分别接一个)读取数据。

特别是这里的数据重复,没太理解。
回复

使用道具 举报

4

主题

20

回帖

32

积分

新手上路

积分
32
 楼主| 发表于 2025-11-7 10:01:13 | 显示全部楼层
SPI 接口是这样的,主机通过两个 SPI 接口,分别读取两个 SPI 从机的数据,主机 MCU 配置了 SPI4 和 SPI6 两个读取接口;两个从机用的是同一套程序,都配置为 SPI4.

数据重复是这样的,主机通过两个 SPI 接口(SPI4, SPI6)同步分别读取两个从机 (两个从机程序逻辑一致的,都是 SPI4) 的数据,比如有时读取数据比如出现这样的情况: 从机1两个不同时刻数据-13094275,13094951;从机2两个不同时刻数据-5061777,5061777,所以出现了重复的情况(从机 1 和从机 2 都会出现),和上面图片1曲线中数据保持不变的情况对应。

针对数据相同的情况,我分析的是,a. 要不就是中断读取的频率处理的不够高(通过定时器触发 ADC 采样产生 DMA 传输完成中断,处理以后再通过 SPI 传输出去),所以通过SPI读取的数据会出现重复,但是我这里已经调节到处理频率最大了,频率再大数据就出现不正常、丢失的情况; b. 主机去同步读取两个 SPI 从机时间太频繁了,所以增加了  bsp_DelayUS (参考主机配置那里 main 函数中逻辑增加了 延时函数),如果不加任何延时,读取重复的数据大约会出现 4 组左右,增加了 bsp_DelayUS 延时,时间设置到 100-1000 us,都会出现 2 组重复的数据,如果到 ms 级别延时,数据又会出现丢失。

我遇到的现象大致是这样的,暂时对这样的现象不是很理解。
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
119430
QQ
发表于 2025-11-8 10:15:30 | 显示全部楼层
剑心v 发表于 2025-11-7 10:01
SPI 接口是这样的,主机通过两个 SPI 接口,分别读取两个 SPI 从机的数据,主机 MCU 配置了 SPI4 和 SPI6  ...

明白你的意思了,图1是正常的,而图2就是有多次重复的问题。

为了解决这个问题,你可以这样测试解决下,不知道适合你的应用不。SPI先发指令过去执行采集,ADC采集设置成DMA NORMAL,采集一次。时间到了主机查询采集完毕没,采集完毕了执行读取。

这种情况下,应该不能再出错了,还要就是关键一点,最好使用硬件片选。
回复

使用道具 举报

4

主题

20

回帖

32

积分

新手上路

积分
32
 楼主| 发表于 2025-11-10 09:38:41 | 显示全部楼层
eric2013 发表于 2025-11-8 10:15
明白你的意思了,图1是正常的,而图2就是有多次重复的问题。

为了解决这个问题,你可以这样测试解决下 ...

我明白了这种方法了,主要是以事件驱动方式去查询,这样确实能保证采样的同步,那我SPI发送指令方式是不是建立一个握手协议方式,然后从机响应回复方式去判定,还有触发采集和停止采集是用这两个函数吗(HAL_ADC_Start_DMA、HAL_ADC_Stop_DMA),还是 Normol 方式只用 Start 函数就可以了,最后就是 ADC 判定是否采集完成我需要怎么去判定呢,是需要用那个位去查询吗,可以给个简单的例子吗
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
119430
QQ
发表于 2025-11-11 11:30:04 | 显示全部楼层
剑心v 发表于 2025-11-10 09:38
我明白了这种方法了,主要是以事件驱动方式去查询,这样确实能保证采样的同步,那我SPI发送指令方式是不 ...

这个我们没做例子。
1、推荐用Normol 方式只用 Start 函数就可以
2、查询是否完成,可以设置查询时间大于采集一轮的时间就行,或者小于的话,得多查询即可。

我觉得这种方式基本上不会出错。容错处理比较方便。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2025-11-22 04:06 , Processed in 0.057505 second(s), 28 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表