多次重复实验得到的时序数据的对齐操作

一、问题的提出

进行多次重复实验会得到多次实验数据,如同世界上没有两片同样的树叶一样,实验过程所需的时间两两之间也不可能完全一样。如何将多次重复实验每个trial进行对齐是进行数据统计的关键步骤之一。本文介绍了一篇Neuron期刊文章上的方法。

上图所示是文章中多次重复实验和数据对齐的实现过程。图A进行正转,B进行反转,分别都进行了7次(指一次trial里转7次,从图片中看出这个示意图中将一个session里包含了18次trial)。图C是某个神经元发放的情形,图D是某块肌肉的情形,图E、F是scaling之后的,图G是图E的均值,曲线是SEM形式,背景颜色表示垂直方向上的速度(即亮表示速度曲线的顶点,暗表示速度曲线的低点)上方的小竖线表示一次转完成。

二、重复实验数据对齐实战

如何实现从图C到图E(或图D到图F)
原文如下:
To ensure response features were not lost to misalignment, we developed a technique to adaptively align trials within a condition. First, trials were aligned on movement onset. Individual trials were then scaled so that all trials had the same duration (set to be the median duration across trials). Because monkeys usually cycled at a consistent speed (within a given condition) this brought trials largely into alignment: e.g., the top of each cycle occurred at nearly the same time for each trial. The adaptive alignment procedure was used to correct any remaining slight misalignments. The time-base for each trial was scaled so that the position trace on that trial closely matched the average position of all trials. This involved a slight non-uniform stretching, and resulted in the timing of all key moments – such as when the hand passed the top of the cycle – being nearly identical across trials. This ensured that high-frequency temporal response features (e.g., the small peak in Figure 1G) were not lost to averaging.

简单讲,就是先对齐movement onset,然后计算duration的均值(这里是中间值),最后把所有trial都拉伸或压缩到均值。当然,在这之前要删除那些偏离太多的trial(这样的trial不能太多,只能是少数,为了达到这样的效果,动物训练要训好)。以下以模拟数据具体实现。

(1)脉冲电信号的对齐实战

计算均值就不说了,直接讲如何进行scaling:
主要的困惑是spike raster是怎么进行的?因为连续信号好弄,直接重采样,matlab里有resample命令,但是没弄过spike信号。

一步一步来:

%先生成一个raster,
X = randn(1,1050);
X(X>0)=0;
X(X<0)=1;
[~,index_spike] = find(X==1);
line([index_spike;index_spike], [0;0.5],'color','k')

得到:
在这里插入图片描述
如何把1050个点scaling到1000个点呢?
(大家最好看看index_spike里的值代表什么含义,没错,就是timestamp,而不是1)
思路:如果在1050的时刻点有个spike,那么scaling之后应该是在1000的时刻点有个与之对应的spike,在原来1000的时刻点有个spike,那么与之对应scaling之后应该是(1000/1050)*1000时刻点,即952.381有个spike,但是采样率是整数,所以近似952时刻点有个spike。看出来了吧,就是(1000/1050)乘以origin时刻点,然后取整,就得到scaling之后的时刻点,就能形成一个新的scaling的raster,我们来实现一下:

scaling_index_spike = (1000/1050)*index_spike;
int_scaling_index_spike = round(scaling_index_spike );
line([int_scaling_index_spike;int_scaling_index_spike], [0;0.5],'color','k')

把scaling前后的raster画在一起:

figure
line([index_spike;index_spike], [0;0.5],'color','k')
figure
line([int_scaling_index_spike;int_scaling_index_spike], [0;0.5],'color','k')

得到结果:
在这里插入图片描述
不要去看横坐标,只看raster图是不是很像啊,完成~

(2)连续信号的对齐实现

%首先生成两个正弦
Y1 = sin(0:0.01:5*pi);
Y2 = sin(0:0.0102:5*pi);
figure;
subplot(211)
plot(1:length(Y1),Y1)
subplot(212)
plot(1:length(Y2),Y2)

得到结果:

从结果可知,Y1由1571个数组成,Y2由1540个数组成
假设那么我们要将二者都scaling到1500个数,怎么操作呢?思路跟上面是一样的,只不过当index相同时,进行均值操作一下。

scaling_index_Y1 = (1500/1571)*(1:1571); %将1571个数与1500个数对应
int_scaling_index_Y1 = round(scaling_index_Y1);  %把小数点去掉
int_scaling_index_Y1 = unique(round(scaling_index_Y1));  %这步没啥特别,实质就是1:1500

for i = 1:length(unique_int_scaling_index_Y1)
    [m n] = find(int_scaling_index_Y1==unique_int_scaling_index_Y1(i));
    scaling_Y1(unique_int_scaling_index_Y1(i)) = mean(Y1(n));
end
%最后画出来瞅瞅
figure
plot(1:length(scaling_Y1),scaling_Y1)

上面完成了Y1的scaling,为了日后方便,我们把上面的操作包装成function:

function scaling_value = scaling_Y(Y, origin_index, scaling_index)
scaling_index_Y1 = (scaling_index/origin_index)*(1:origin_index); %将1571个数与1500个数对应
int_scaling_index_Y1 = round(scaling_index_Y1);  %把小数点去掉
unique_int_scaling_index_Y1 = unique(round(scaling_index_Y1));  %这步没啥特别,实质就是1:1500

for i = 1:length(unique_int_scaling_index_Y1)
    [m n] = find(int_scaling_index_Y1==unique_int_scaling_index_Y1(i));
    scaling_value(unique_int_scaling_index_Y1(i)) = mean(Y(n));
end
%最后画出来瞅瞅
figure
plot(1:length(scaling_value),scaling_value)

调用计算Y2的scaling:

scaling_Y2 = scaling_Y(Y2, length(Y2), 1500)

得到结果:

最后,为了直观,我们把Y1,Y2和scaling_Y1, scaling_Y2画在一起:

figure
subplot(511)
plot(1:length(Y1),Y1)
subplot(512)
plot(1:length(Y2),Y2)
subplot(513)
plot(1:length(scaling_Y1),scaling_Y1)
%set(gca,'xtick',0:1600:1600);
subplot(514)
plot(1:length(scaling_Y2),scaling_Y2)
%set(gca,'xtick',0:1600:1600);
subplot(515)
plot(1:length(Y1),Y1)
hold on
plot(1:length(Y2),Y2)
plot(1:length(scaling_Y1),scaling_Y1)
plot(1:length(scaling_Y2),scaling_Y2)

得到结果:

<think>嗯,用户之前让我讲解了STM32的GPIO知识点,现在他们又给了一个更广泛的考纲,包括GPIO、时钟、中断、定时器、ADC、DAC以及I2C和SPI通信。用户希望根据这个考纲推测可能出现的题目并进行解析。我需要先理解每个部分的基础和常见考点。 首先,用户可能是在准备考试或者面试,需要复习STM32的基础外设知识。他们可能对每个模块的基本操作和常见应用场景不太熟悉,或者想通过题目来检验自己的掌握程度。之前的回复是关于GPIO的详细讲解,现在需要扩展到更多外设,所以需要覆盖每个模块的核心知识点。 接下来,考虑各个部分的基础实用题目类型。例如,GPIO部分可能会考模式选择和配置步骤,时钟部分涉及时钟树的配置和常见问题,中断可能包括配置流程和优先级处理。定时器可能涉及PWM生成和定时计算,ADC和DAC的基础应用和配置。通信协议方面,I2C和SPI的时序图和编程实现可能是重点。 需要确保每个知识点都涵盖初始化步骤、函数调用以及常见问题。比如,GPIO的输入输出模式区别,时钟配置中的分频系数,中断服务函数的编写注意事项,定时器的自动重装载值计算,ADC的参考电压选择,I2C的起始停止信号等。此外,用户可能对实际应用中的问题解决感兴趣,比如如何避免通信中的冲突,如何处理ADC采样时的噪声等。 还要注意用户提到的“基础实用题”,所以题目不宜过难,但需要覆盖必须掌握的内容。例如,如何配置一个PWM输出,如何读取ADC的值,I2C和SPI的初始化代码示例等。解析部分需要简明扼要,指出关键步骤和常见错误,帮助用户理解和记忆。 可能需要检查是否每个外设都涵盖了初始化流程、关键函数和典型应用场景。同时,结合之前GPIO的详细讲解,保持一致的风格,使用代码示例和实际场景举例,帮助用户更好地理解。另外,注意提醒用户查阅数据手册和参考手册,因为这在开发中非常重要。 最后,总结时要强调重点,建议通过实验来巩固知识,比如使用开发板进行实际操作,观察现象,调试问题。这能帮助用户将理论应用到实践中,加深理解。</think>以下是针对该考纲可能出现的 **基础实用题型** 及其解析,涵盖 STM32 的核心外设和通信协议: --- ### **1. GPIO 基础操作** #### **题目示例** **Q1**:如何配置 PA5 引脚为推挽输出模式,并控制其输出高电平? **解析**: ```c // 步骤1:开启 GPIOA 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 步骤2:配置 GPIO 模式 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度 GPIO_Init(GPIOA, &GPIO_InitStruct); // 步骤3:输出高电平 GPIO_SetBits(GPIOA, GPIO_Pin_5); ``` **关键点**: - 必须开启 GPIO 时钟(初学者常忽略)。 - 输出速度影响边沿陡峭度和功耗(高频干扰大时需降低速度)。 --- ### **2. 时钟配置** #### **题目示例** **Q2**:如何配置 STM32 的 HSE(外部高速时钟)为系统时钟? **解析**: ```c // 1. 开启 HSE 并等待就绪 RCC_HSEConfig(RCC_HSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); // 2. 配置 PLL:HSE 作为 PLL 输入,倍频为 9 倍(假设 HSE=8MHz → PLL=72MHz) RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 3. 开启 PLL 并等待就绪 RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); // 4. 切换系统时钟到 PLL RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() != 0x08); // 检查切换是否成功 ``` **关键点**: - 时钟树是 STM32 的核心,需理解 HSE/HSI/PLL 的关系。 - 系统时钟频率影响外设工作频率(如定时器、UART 波特率)。 --- ### **3. 中断配置** #### **题目示例** **Q3**:如何配置 PA0 引脚为下降沿触发的外部中断? **解析**: ```c // 1. 配置 PA0 为上拉输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStruct); // 2. 绑定 PA0 到 EXTI0 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // 3. 配置 EXTI0 为下降沿触发 EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line = EXTI_Line0; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); // 4. 配置 NVIC(设置优先级和使能) NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); ``` **关键点**: - 中断服务函数(ISR)需清除中断标志,否则会重复触发。 - 优先级分为抢占优先级和子优先级,数值越小优先级越高。 --- ### **4. 定时器(TIM)应用** #### **题目示例** **Q4**:如何配置 TIM2 为 PWM 输出模式,占空比 50%,频率 1kHz(假设系统时钟 72MHz)? **解析**: ```c // 1. 开启 TIM2 和 GPIOA(假设 PA0 为 TIM2_CH1) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置 PA0 为复用推挽输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // 3. 配置 TIM2 的时基单元 TIM_TimeBaseInitTypeDef TIM_InitStruct; TIM_InitStruct.TIM_Prescaler = 72 - 1; // 分频后时钟 = 72MHz / 72 = 1MHz TIM_InitStruct.TIM_Period = 1000 - 1; // 自动重装载值,频率 = 1MHz / 1000 = 1kHz TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_InitStruct); // 4. 配置 PWM 模式(通道1) TIM_OCInitTypeDef TIM_OCInitStruct; TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStruct.TIM_Pulse = 500; // 占空比 = 500/1000 = 50% TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStruct); // 5. 启动 TIM2 TIM_Cmd(TIM2, ENABLE); ``` **关键点**: - PWM 频率由 `Prescaler` 和 `Period` 共同决定。 - 占空比由 `TIM_Pulse`(比较值)与 `Period` 的比值决定。 --- ### **5. ADC 与 DAC 基础** #### **题目示例** **Q5**:如何配置 ADC1 的通道0(PA0)进行单次采样? **解析**: ```c // 1. 配置 PA0 为模拟输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStruct); // 2. 配置 ADC1 ADC_InitTypeDef ADC_InitStruct; ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // 独立模式 ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 单通道扫描关闭 ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换 ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发 ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐 ADC_Init(ADC1, &ADC_InitStruct); // 3. 配置 ADC 通道0 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // 4. 开启 ADC 并校准 ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); // 5. 启动转换并读取结果 ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换完成 uint16_t adc_value = ADC_GetConversionValue(ADC1); ``` **关键点**: - 校准是必须步骤,否则采样值可能不准确。 - ADC 参考电压(VDDA)需稳定(通常需硬件滤波)。 --- ### **6. I2C/SPI 通信时序编程** #### **题目示例** **Q6**:使用软件模拟 I2C 协议,写出起始信号和停止信号的代码。 **解析**: ```c // 假设 SDA=PB7,SCL=PB6 #define SDA_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_7) #define SDA_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_7) #define SCL_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_6) #define SCL_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_6) // I2C 起始信号 void I2C_Start(void) { SDA_HIGH(); // SDA 初始为高 SCL_HIGH(); Delay_us(5); // 保持时间 SDA_LOW(); // SDA 拉低 Delay_us(5); SCL_LOW(); // SCL 拉低,完成起始信号 } // I2C 停止信号 void I2C_Stop(void) { SCL_LOW(); SDA_LOW(); Delay_us(5); SCL_HIGH(); Delay_us(5); SDA_HIGH(); // SDA 在 SCL 高电平时变高,表示停止 } ``` **关键点**: - I2C 起始信号:SCL 高电平期间,SDA 从高→低。 - 停止信号:SCL 高电平期间,SDA 从低→高。 --- ### **总结** 1. **基础题型核心**: - 初始化流程(时钟→模式配置→外设参数设置→使能)。 - 关键函数(如 `GPIO_Init()`, `TIM_TimeBaseInit()`, `ADC_RegularChannelConfig()`)。 - 时序控制(I2C/SPI 的起始、停止、数据发送)。 2. **高频考点**: - GPIO 模式选择(推挽 vs 开漏)。 - 定时器 PWM 参数计算(频率、占空比)。 - ADC 校准与参考电压。 - I2C 的 ACK/NACK 机制。 3. **实践建议**: - 通过开发板验证代码(如 LED 呼吸灯、按键中断、ADC 电压采集)。 - 使用逻辑分析仪观察 I2C/SPI 时序波形。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值