目录
1.C语言中static、const、volatile关键字用法区别;
static的用法(定义和用途)
const的用法(定义和用途)
volatile (英文意思为易变的) 作用和用法:
2.中断
中断的含义和类型
中断优先级的分类和区别
有抢占优先级为什么还要子优先级?
什么是中断嵌套,stm32中中断嵌套是怎么实现的
中断结构
中断向量表的存储在什么地方
中断向量表的大小
中断使用方法
什么是DMA,他有什么用处
3.常用操作
设置一绝对地址为0x67a9的整型变量的值为0xaa66
实现某一位置0或置1操作,保持其它位不变
利用移位、与实现模
4.中断函数中的注意问题
5.什么是不可重入函数?
6.如何写出可重入的函数?
7.malloc内存分配
8.stm32和gd32的相同点和不同点?
相同点
不同点
9.MCU可以运行Linux吗,为什么
10.STM32启动过程(上电开始->main执行的过程)
11.如何保证编译后的代码能够烧写到芯片的正确地址中:
12.中断响应执行流程,中断上下文指的什么,保存中断上下文是完成的什么操作,以STM32为例,都有哪些寄存器被保存
13.STM32常见寄存器:R13(SP,堆栈指针),R14(LR,连接寄存器),R15(PC程序计数器)的作用
1)用static修饰局部变量:使其变为静态存储方式(静态数据区),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。
2)用static修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。
3)用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用 静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
const主要用来修饰变量、函数形参和类成员函数:
1)用const修饰常量:定义时就初始化,以后不能更改。
2)用const修饰形参:func(const int a){};该形参在函数里不能改变
3)用const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
1.const int a;
2.int const a;
3.const int *a;
4.int * const a;
5.int const * a const;
前两个的作用是一样,a是一个常整型数。
常指针从右往左读,以*为分界
第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。
第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。
最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量在内存中的值,而不是使用保存在寄存器里的备份(虽然读写寄存器比读写内存快)。
以下几种情况都会用到volatile:
并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量
多线程应用中被几个任务共享的变量
中断是计算机系统中的一种机制,用于处理突发事件或异步事件。在计算机运行过程中,CPU通常是按照指令的顺序依次执行的,但是当发生某些特殊事件时,如外部设备的输入、定时器的溢出等,CPU需要立即中断当前的任务,转而处理这些事件。这样可以提高系统的响应速度和效率。
中断可以分为硬件中断和软件中断两种类型。
硬件中断是由外部设备触发的,如键盘输入、鼠标点击等。当外部设备触发了中断信号时,CPU会立即停止当前的任务,转而执行与中断相关的处理程序。处理完中断后,CPU会返回到之前被中断的地方继续执行。
软件中断是由程序中的特殊指令触发的。程序可以通过软件中断指令,主动请求CPU中断当前任务,执行与中断相关的处理程序。软件中断可以用来实现特定的功能,如操作系统的系统调用。
中断优先级可以分为两个级别:抢占优先级和子优先级。
抢占优先级(Preemption Priority):抢占优先级决定了当多个中断同时发生时,哪个中断可以中断当前正在执行的中断。具有较高抢占优先级的中断可以打断正在执行的较低优先级中断,并立即执行自己的中断服务程序。
子优先级(Subpriority):子优先级用于决定在同一抢占优先级的多个中断中,哪个中断将首先得到执行。具有较高子优先级的中断将在同一抢占优先级的其他中断之前得到执行。
抢占优先级和子优先级的组合可以提供更灵活的中断控制和调度。抢占优先级主要用于处理多个中断同时发生时的中断抢占关系,而子优先级则用于处理同一抢占优先级的多个中断的执行顺序。
使用抢占优先级可以确保关键的中断能够及时中断正在执行的低优先级中断,并立即执行自己的中断服务程序。这对于实时性要求较高的应用非常重要。
然而,当多个中断具有相同的抢占优先级时,如果没有子优先级的支持,它们将按照先后顺序依次执行,无法进行更细粒度的调度。
通过使用子优先级,可以在同一抢占优先级的多个中断中确定首先执行的中断。这对于需要优先处理某些特定中断的应用非常有用。
中断嵌套是指在一个中断服务程序(ISR)执行期间,另一个中断发生并触发了相应的中断服务程序的执行。当一个中断正在处理时,如果有更高优先级的中断发生,系统将暂停当前中断的处理,转而去处理更高优先级的中断,这就是中断嵌套。
在STM32微控制器中,中断嵌套是通过抢占优先级和子优先级来实现的。当一个中断正在执行时,如果有更高抢占优先级的中断请求发生,系统会立即中断当前中断的执行,并开始处理更高优先级的中断。如果多个中断具有相同的抢占优先级,那么子优先级将决定它们的执行顺序。
中断向量表(Interrupt Vector Table):中断向量表是一个存储中断向量地址的数据结构,用于存储中断服务函数的入口地址。当一个中断发生时,微控制器会根据中断号从中断向量表中读取相应的ISR地址,并跳转到该地址执行中断服务程序。
在STM32中,中断向量表存储在内部闪存的起始地址处。
中断向量表的大小取决于微控制器支持的中断数量。对于STM32系列微控制器,通常采用的是基于向量表的中断处理方式,其中中断向量表的大小是固定的,每个中断向量的大小是4个字节。因此,中断向量表的大小等于中断数量乘以4个字节。
介绍一下中断控制器
中断控制器(Nested Vectored Interrupt Controller,NVIC):NVIC是STM32中断控制器的核心组件,用于管理和控制中断。它支持多级中断优先级,可以配置中断优先级、使能或禁用中断,并提供中断向量表的地址。
NVIC有以下几个主要的功能:
中断优先级管理:NVIC允许为每个中断分配一个抢占优先级和一个子优先级。抢占优先级用于确定中断的抢占关系,而子优先级用于确定同一抢占优先级的多个中断的执行顺序。NVIC提供了寄存器来配置和管理中断的优先级。
中断使能/禁止控制:NVIC提供了寄存器来使能或禁止特定的中断。通过设置相应的位,可以选择性地使能或禁止中断。这对于灵活地控制中断的触发和执行非常有用。
中断状态管理:NVIC提供了寄存器来管理中断的状态。例如,可以通过读取和写入中断挂起寄存器来判断中断是否处于挂起状态,以及通过写入中断清除寄存器来清除中断标志。
中断向量表偏移:NVIC允许通过设置偏移量来修改中断向量表的起始地址。这对于实现中断向量重定向非常有用,可以将中断重定向到其他地址。
中断服务函数(Interrupt Service Routine,ISR):中断服务函数是中断发生时执行的代码块。在STM32中,中断服务函数需要使用特定的函数声明和命名规则,并通过中断向量表进行注册。
配置中断优先级:首先,需要使用NVIC_SetPriority()函数设置中断的抢占优先级和子优先级。该函数的参数包括中断通道号和优先级值。
初始化中断向量表:在启动代码中,需要初始化中断向量表的起始地址。可以使用NVIC_SetVectorTable()函数来设置中断向量表的偏移地址。
注册中断处理函数:使用NVIC_Init()函数注册中断处理函数。该函数的参数包括中断通道号、中断优先级和中断处理函数的地址。
使能中断:使用NVIC_EnableIRQ()函数使能中断。该函数的参数为中断通道号。
编写中断处理函数:编写中断处理函数,处理中断事件发生时的逻辑。中断处理函数的命名和参数取决于所使用的中断通道和编程语言。
#include "stm32f10x.h"
// 中断处理函数
void EXTI0_IRQHandler(void)
{
// 处理中断事件逻辑
// ...
// 清除中断标志
EXTI_ClearITPendingBit(EXTI_Line0);
}
int main(void)
{
// 初始化中断向量表
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
// 配置中断优先级
NVIC_SetPriority(EXTI0_IRQn, 0);
// 注册中断处理函数
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能中断
NVIC_EnableIRQ(EXTI0_IRQn);
while (1)
{
// 主循环逻辑
// ...
}
}
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。
对于大容量的STM32芯片有2个DMA控制器 两个DMA控制器,DMA1有7个通道,DMA2有5个通道。
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:
外设到内存
内存到外设
内存到内存
外设到外设
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;
void bit_set(unsigned char *p_data, unsigned char position, int flag)
{// p_data是指定的源数据;position是指定位(取值范围为1~8);flag表示置0还是置1操作。
int a = 1<<(position-1);
if (flag)
{
*p_data |= a;
}
else
{
*p_data &= ~a;
}
}
a=b*2;a=b/4;a=b%8;a=b/8*8+b%4;a=b*15;实现效率最高的算法
a=b*2 -> a=b<<1;
a=b/4 -> a=b>>2;
a=b%8 -> a=b&7; // b % n -> b & (n - 1)
a=b/8*8+b%4 -> a=((b>>3)<<3)+(b&3)
a=b*15 -> a=(b<<4)-b
无符号与有符号相加结果为无符号类型
__interrupt void compute_area (void)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
ISR不可能有参数和返回值的!
2、 ISR尽量不要使用浮点数处理程序,浮点数的处理程序一般来说是不可重入的,而且是消耗大量CPU时间的!
函数体内使用了静态(static)的数据结构;
函数体内调用了 malloc() 或者 free() 函数;
函数体内调用了标准 I/O 函数;
printf函数一般也是不可重入的,UART属于低速设备,printf函数同样面临大量消耗CPU时间的问题!
不可重入函数在实现时候通常使用了全局的资源,在多线程的环境下,如果没有很好的处理数据保护和互斥访问,就会发生错误,常见的不可重入函数有:
printf --------引用全局变量stdout
malloc --------全局内存分配表
free --------全局内存分配表
满足下列条件的函数多数是不可重入的:
(1)函数体内使用了静态的数据结构;
(2)函数体内调用了malloc()或者free()函数;
(3)函数体内调用了标准I/O函数。
在函数体内不访问那些全局变量;
如果必须访问全局变量,记住利用互斥信号量来保护全局变量。或者调用该函数前关中断,调用后再开中断;
不使用静态局部变量;
坚持只使用缺省态(auto)局部变量;
在和硬件发生交互的时候,切记关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”或者用 OS_ENTER_KERNAL/OS_EXIT_KERNAL 来描述;
不能调用任何不可重入的函数;
谨慎使用堆栈。最好先在使用前先 OS_ENTER_KERNAL;
malloc申请大小问题
malloc申请一段长度为0的空间,malloc依然会返回一段地址,还有一段地址空间,所以ptr不等于NULL。
malloc这个函数,会有一个阈值,申请小于这个阈值的空间,那么会返回这个阈值大小的空间。这个阈值会随着编译器的不同而不同
如果申请一个负数,那么返回的是0,这是因为malloc规定不可以申请一个负数
1.芯片的型号命名方式相同,而且相同型号的引脚定义基本相同,
2.函数库文件基本相同:因为GD32正向研发,对于PIN TO PIN的芯片,内部寄存器地址和STM32完全相同,所以STM32的库文件编译后的文件可以直接下载。
3.编译工具相同如keil、IAR都相同。
1.工作电压不同:
外部供电:GD32外部供电范围是2.6~3.6V,STM32外部供电范围是2.0~ 3.6V或1.65~3.6V。GD的供电范围比STM32相对要窄一点。
内核电压:GD32内核电压是1.2V,STM32内核电压是1.8V。GD的内核电压比STM32的内核电压要低,所以GD的芯片在运行的时候运行功耗更低。
2.内核和频率不同
GD32采用二代的M3内核,STM32主要采用一代M3内核,
使用HSE(高速外部时钟):GD32的主频最大108M,STM32的主频最大72M
使用HSI(高速内部时钟):GD32的主频最大108M,STM32的主频最大64M
主频大意味着单片机代码运行的速度会更快,项目中如果需要进行刷屏,开方运算,电机控制等操作,GD是一个不错的选择。
3.功耗和串口方面
GD的产品在相同主频情况下,GD的运行功耗比STM32小,但是在相同的设置下GD的停机模式、待机模式、睡眠模式比STM32还是要高的。即GD32的静态功耗要相对高一点
a:睡眠模式 Sleep:GD32F:12.4mA,STM32F10X: 7.5mA
b:深度睡眠模式 Deep Sleep: GD32F: 1.4mA,STM32F10X: 24uA
c:待机模式 Stand By: GD32F: 10.5uA,STM32F10X: 3.4uA
d:运行功耗: GD32F: 32.4mA/72M,STM32F10X: 52mA/72M
GD在连续发送数据的时候每两个字节之间会有一个Bit的Idle,而STM32没有。GD的串口在发送的时候停止位只有1/2两种停止位模式。STM32有0.5/1/1.5/2四种停止位模式。GD 和STM32 USART的这两个差异对通信基本没有影响,只是GD的通信时间会加长一点。
4.启动时间:二者启动时间相同,由于GD32运行稍快,需要延长上电时间,配置(2ms) 。
5.GD32提高了相同工作频率下的代码执行速度,所以GD32的_NOP()时间比STM32更加短,所以不使用定时器做延时时要注意修改。
6.GD32的flash擦除时间要比STM32更长。
Flash擦除时间:GD32是60ms/page,STM32 30ms/page
7.GD32的BOOT0必须接10K下拉或接GND,ST可悬空。
BOOT0 管脚: Flash 程序运行时,BOOT0在STM32上可悬空,GD32必须外部下拉(从 Flash 运行,BOOT0必须下拉地)。
8.GD32对时序要求严格,配置外设需要先打开时钟,否则可能导致外设无法配置成功;STM32的可以先配置再开时钟。
9.需要修改外部晶振起振超时时间,不用外部晶振可跳过这步。
原因:GD32与STM32的启动时间存在差异,为了让GD32 MCU更准确复位(不修改可能无法复位)。
10.ADC不同点: GD32的输入阻抗和采样时间的设置和STM32有一定差异,相同配置GD32采样的输入阻抗相对来说要小。
MCU(Microcontroller Unit,微控制器)通常不支持运行完整的Linux操作系统,因为它们的处理能力和存储容量有限。Linux操作系统需要较高的处理能力和存储容量,通常需要至少几百兆字节的存储空间和数百兆赫的处理器速度。而MCU通常只有几十兆赫的处理器速度和几十KB的存储空间,无法满足Linux操作系统的要求。
但是,一些高端的MCU,如ARM Cortex-A系列,可以支持运行Linux操作系统。这些MCU具有更高的处理能力和存储容量,可以满足Linux操作系统的要求。但是,这些MCU通常比普通的MCU更昂贵,也需要更多的功耗和散热措施。
stm32不可以跑linux;linux系统是运行单位是进程,而ucos运行单位是线程,要实现进程芯片必须有MMU,也即存储管理单元,而stm32是不带存储管理单元的,所以不能运行进程的操作系统,也就不能运行linux。
1.硬件处理能力受限
STM32芯片的处理能力有限,与运行Linux所需要的计算资源相比较弱。Linux系统需要大量的内存和处理器能力来完成各种任务,而STM32芯片的内存和处理器速度都比较有限。
2.内核架构区别
STM32系列微控制器通常使用ARM Cortex-M内核,而Linux主要是为支持ARM Cortex-A内核的处理器设计的,这两种内核在架构和功能上存在较大差异。ARM Cortex-M内核专注于低功耗、实时性和可裁剪性,适用于嵌入式系统和物联网应用。它通常具有较小的存储器容量和较低的计算能力,无法满足运行复杂的操作系统所需的资源要求。相比之下,ARM Cortex-A内核用于高性能应用,如智能手机、平板电脑和服务器。它拥有更强大的计算能力和较大的存储器容量,可以支持运行像Linux这样的完整操作系统。
3.外设数量受限
STM32芯片上的外设数量有限,这与需要较完整的设备支持的Linux系统形成了鲜明对比。Linux系统需要庞大的驱动程序支持,以适应各种不同型号的硬件设备。而STM32芯片的外设数量有限,需要与丰富的外围设备进行兼容性处理,这是相对困难的。
10.MCU工作的基本原理
一个MCU一般由CPU(中央处理单元)、地址空间、片上外设三个部分组成。CPU是处理器的核心,它控制着整个系统的运作。 地址空间则是一个抽象的概念,它是CPU访问外设和内存的接口。如果一定要对应一个物理设备的话,我想应该是芯片内部的地址总线和数据总线。 片上外设以及芯片内部的各种存储设备都会被映射到地址空间中,使得我们在编写程序的时候只需要对地址空间中的某一段进行读写就可以操控外设了。 片上外设实际上是一个集合,指的是MCU芯片内部为实现某一特殊功能而专门设计的模块,比如说串口、GPIO等等,它们都会映射到地址空间中。
一个芯片上有很多引脚,它们由片上外设控制,是连接芯片内部与用户设备之间的桥梁。 每个引脚的功能基本都是确定的,不过为了节省资源同一个引脚往往对应几个不同的功能。所以具体一个引脚是用作串口还是输出PWM信号,除了由其物理属性决定之外, 还需要在程序中通过软件进行配置。通过正确的连接和配置引脚的功能,合理的操控片上外设,我们就可以控制LED,驱动SD卡,进而实现期望的功能。
作为一个计算机系统的核心,CPU的实际工作就是取指令和计算。大体上它可以看做是三个部分组成的:寄存器组、算术逻辑单元(ALU)、指令队列。 在Cortex-M4的寄存器中有一个特殊的寄存器PC(Program Counter,程序计数器), 用于控制程序的执行。在每个时钟周期中CPU都会根据PC中的值,从地址空间中取一条指令放到指令队列中, 同时从指令队列中取出一条指令进行解析和运算并把上次的运算结果写到寄存器中。
CPU运算所用的指令和数据都来自地址空间。在Cortex-M4的内存系统中, CPU可以访问4G的地址空间,根据所映射的物理对象不同大体上被划分成了6块。我们烧写到芯片内部的程序一般都在其中的Code段中, 在STM32中这个段对应的是一块FLASH。上电的时候,基本上只有这块FLASH中的内容是确定的,其它地址空间以及CPU内部的寄存器中的值都是随机的。 当然为了防止上电的时候外设产生意外,片上外设(Peripheral)段中的值在上电的时候也会有初始值。
所以,从处理器的角度来看,启动过程实际上是给各个寄存器赋初值的过程,更具体的是给PC寄存器赋初值的过程。从MCU和系统的角度来看, 启动过程是初始化处理器和外设的过程。
首先对栈和堆的大小进行定义,并在代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址。然后在复位中断服务程序中跳转C/C++标准实时库的__main函数,完成用户堆栈等的初始化后,跳转.c文件中的main函数开始执行C程序。
STM32启动过程可以分为以下几个步骤:
1. 上电复位:当STM32芯片上电时,会自动执行复位操作,将所有寄存器和内存初始化为默认值。
2. 系统时钟初始化:在复位后,STM32会使用内部RC振荡器作为系统时钟源,此时系统时钟频率为默认值。如果需要使用外部时钟源,需要进行时钟初始化设置。
3. 外设初始化:根据需要,初始化各个外设模块,如GPIO、USART、SPI等。
4. 应用程序初始化:在外设初始化完成后,进行应用程序的初始化,包括变量初始化、中断初始化等。
5. 进入main函数:当应用程序初始化完成后,进入main函数,开始执行应用程序。
假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。
在项目链接选项中指定只读代码段的起始地址,并标记向量表的符号__Vectors作为代码的起始符号;
在启动文件把向量表定义为只读的代码段并用__Vectors标识。
在STM32中,中断响应执行流程如下:
1. 当外部中断或内部中断发生时,CPU会立即停止当前正在执行的指令,保存当前的现场(包括程序计数器、状态寄存器等)。
2. CPU会跳转到中断向量表中对应的中断处理函数的地址,开始执行中断处理函数。
3. 中断处理函数会根据中断类型进行相应的处理,例如读取外设数据、清除中断标志位等。
4. 中断处理函数执行完毕后,会使用返回指令(例如BX LR)返回到原来的程序执行位置,恢复之前保存的现场。
5. CPU继续执行原来的程序,从中断响应之前的位置继续执行。
中断上下文:
中断上下文是指在CPU执行中断处理程序之前,需要保存当前被中断的程序的执行现场,包括程序计数器、寄存器、标志寄存器等状态信息,以便在中断处理程序执行完毕后,能够恢复被中断程序的执行现场,继续执行。
(1)中断上文:硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上文可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境。
(2)中断下文:执行在内核空间的中断服务程序。
保存中断上下文是指:在中断处理程序执行之前,需要将被中断程序的执行现场保存到内存中,以便在中断处理程序执行完毕后,能够将被中断程序的执行现场恢复回来,继续执行。这个操作通常由操作系统内核完成,需要保证保存和恢复中断上下文的正确性和可靠性,以避免中断处理程序对被中断程序的影响。
1. R13(SP,堆栈指针):用于指向当前堆栈的顶部,即最后一个压入堆栈的数据的地址。在函数调用时,会将当前函数的返回地址、参数等信息压入堆栈中,因此堆栈指针的值会随着函数调用的深度而不断变化。
2. R14(LR,连接寄存器):用于存储函数调用指令的下一条指令的地址,即函数返回时需要跳转到的地址。在函数调用时,会将当前函数的返回地址存储到连接寄存器中,然后跳转到被调用函数的入口地址执行。
3. R15(PC程序计数器):用于存储当前正在执行的指令的地址。在程序执行时,PC会不断自增,指向下一条要执行的指令的地址。当遇到函数调用、分支跳转等指令时,PC的值会被修改,以跳转到相应的地址执行。
14.SPI有哪四种工作模式
SPI(Serial Peripheral Interface)有以下四种工作模式: 1. 模式0:时钟空闲时为低电平,数据在时钟的上升沿采样,下降沿输出。 2. 模式1:时钟空闲时为低电平,数据在时钟的下降沿采样,上升沿输出。 3. 模式2:时钟空闲时为高电平,数据在时钟的下降沿采样,上升沿输出。 4. 模式3:时钟空闲时为高电平,数据在时钟的上升沿采样,下降沿输出。