IIC支持热拔插吗?(附有详细CW32 IIC协议解读) 不知道你学会没有?我在这些低速实验写完以后会给出逻辑分析仪的调试步骤。
IIC写了,接下来就是SPI,是值得大写特写的东西,因为绝大多数的器件都是SPI的,简直就是ALL IN了。
我觉得大家对待协议也不需要头疼,这也是人设计的,抓住核心的观念就能举一反三。比如一个协议里面的数据,0和1究竟是如何传输的。没错就是这样简单的观念。以及知道每次传输的帧是多大,如果小于,就补0占位,大于就要分多帧。
SPI 的数据传输以帧为单位,每帧的数据位宽通常为 8 位(也可配置为 16 位或更多)。
数据从 高位 (MSB) 或 低位 (LSB) 开始发送,这取决于设备配置。
其实SPI的特点是自由,简单和高速
引脚标准是4个,有很多的变种。
比如QSPI,就4个数据线一起来输出,一个时钟沿就4bit
SPI 通过时钟信号(SCLK)同步数据传输。
SCLK先看,然后再看稳定的采样在哪里
俩俩配对就4种
单从机通信
主机将目标从机的 SS/CS 引脚拉低(有效),其他从机保持不工作状态。
完成通信后,主机将 SS/CS 引脚拉高(无效)。
多从机通信
每个从机分配一个独立的 SS/CS 引脚。
主机通过控制对应从机的 SS/CS 引脚来选择目标从机。
可以把多个SPI器件的SCK,DIN,DOUT接入外设口,CS分别接入,一旦上面拉低,主机就知道是这个器件要通讯了。
下面内容特别棘手,先看个通俗解读。
第一次交流:顾客对厨师说:“我要一个汉堡。” 厨师当时无法立刻提供汉堡,但会随口说点闲话(无意义数据)。 第二次交流:顾客再问一句“我的汉堡好了吗?”(占位数据),厨师这时已经准备好汉堡并递给顾客(返回有效数据)。
这个颜色就很能说明问题,灰色就是其实在发送
SPI通讯的核心是,同步发送。
同步
然后再来看看SPI在不同的MCU里面的体现:
NRF5280,原来你小子也叫IIC为TWI
可以看到SPI因为比IIC自由和高效,更加的被人喜欢
在SPI通信期间,数据的发送(串行移出到MOSI/SDO总线上)和接收(采样或读入总线(MISO/SDI)上的数据)同时进行。串行时钟沿同步数据的移位和采样。
每次时钟的一个边沿,两个引脚就会交换一次
在每个 Clock 周期内,SPI 设备都会发送并接收一个 bit 大小的数据(不管主设备好还是从设备),相当于该设备有一个 bit 大小的数据被交换了。
假设主设备发送数据 0b1010,从设备在同时会返回数据 0b0101。
这个图是真好,说明了这个循环的过程
MicroChip的外设
旋转交换
一个 Slave 设备要想能够接收到 Master 发过来的控制信号,必须在此之前能够被 Master 设备进行访问 (Access)。
所以,Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。
在数据传输的过程中,每次接收到的数据必须在下一次数据传输之前被采样。如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致 SPI 物理模块最终失效。
每个 SPI 外设(无论主从)都拥有发送和接收寄存器。 每次 SPI 时钟周期完成时,接收到的数据会存储到接收寄存器中,发送寄存器中的数据则被发送出去。
因此,在程序中一般都会在 SPI 传输完数据后,去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的(虽然发送后紧接着的读取是无意义的,但仍然需要从寄存器中读出来)。
每次接收数据后,程序必须从接收寄存器中读取数据,否则新的数据到来时会覆盖原有数据(丢失数据)。
这就是为什么即使程序中对某些数据不关心(例如占位数据 Dummy Data),也必须通过代码将其读取出来。
写操作的同时会接收 Dummy 数据:
读操作的同时需要发送 Dummy 数据:
当主设备发送数据给从设备时,从设备的 MISO 引脚也会输出数据(可能是无意义的 Dummy 数据,比如 0xFF
或 0x00
)。
如果主设备只想单纯“写数据”,需要忽略接收到的数据。
当主设备需要从从设备读取数据时,必须通过 MOSI 发送一些无意义的占位数据(Dummy 数据,比如 0xFF
或 0x00
),以触发时钟信号驱动从设备的数据传输。
这样,主设备的接收寄存器中才能获取到从设备的有效数据。
CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。
数据显示在MOSI和MISO线上。一发一收。
传输的开始和结束用绿色虚线表示,采样边沿用橙色虚线表示,移位边沿用蓝色虚线表示。
在SPI通信中,数据的“移出”是指数据从发送寄存器中被传输到MOSI(主输出从输入)引脚或MISO(主输入从输出)引脚,最终被对方设备接收到。
我们在边沿的角度再看看传输过程。
一个设备的采样边沿,对应另一个设备的移位边沿。 比如:若采样在时钟的上升沿,则数据通常在下降沿移出。
再从各个部件来看:
SPI设备内部:
SPI寄存器中存储了要发送的数据。
在时钟的下降沿时,寄存器中的数据会被移出到硬件传输逻辑,并通过硬件驱动电路输出到引脚上(MOSI或MISO)。
SPI总线:
数据被传输到总线上(MOSI或MISO线),随着时钟信号的变化而发送到对方设备。
同时,对方设备也会根据时钟的上升沿采样接收该数据。
对方设备:
接收数据的一方通过时钟的上升沿对总线上的数据采样,将接收到的数据信号存储到其自身的接收寄存器中。
可以挂多个设备
连接的方法有很多-MSPM0取图
TI自己取了新名字,反正写的也是比较通俗的
TI这个真难读,数据手册,不说人话。
老样子,我们从CW32的参考手册看起。
串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信。
CW32L010 内部集成 1 个串行外设 SPI 接口,支持双向全双工、单线半双工和单工通信模式,可配置 MCU 作为主机或从机,支持多主机通信模式。
速度快:SPI 通信速度可达几十 Mbps,比 I²C 更高效。 全双工:支持同时发送和接收数据。 简单协议:没有复杂的握手或地址机制。
这是特色功能
这个框图非常简单了,就是发送和移位形成一个环,然后一个时钟和CS
先看GPIO,时钟就是输出嘛,还是要驱动能力的,推挽
非常的全面,CH的数据手册也有这个
SPI 的通信时序,CS、SCK、MOSI 信号都由主机控制产生,MISO 信号是从机响应信号。
从机选择 CS 信号由高电平变低,是 SPI 通信的起始信号,当从机检测到起始信号后,开始与主机通信。
在每个 SCK 的时钟周期中,MOSI 和 MISO 信号线传输一位数据。CS 由低电平变高,是通信的停止信号,表示本次通信结束。
你觉得这个话里面平平无奇是吧?
我考考你,这个时钟是一直都有?还是就通讯的时候才有时钟?
很明显是,主机在给时钟的情况下,数据同步的发出来
可以继续放大看
由主机生成并提供给从机。 用于同步主机和从机之间的数据传输。 数据的采样时刻由时钟信号的沿(上升沿或下降沿)决定。 从机仅被动接收时钟信号,不能主动生成时钟。
时钟信号(SCK)在通信开始时由主机激活,当通信结束时停止。 在非通信状态下,SCK 保持在空闲电平(由时钟极性 CPOL 决定)。
后边就是下降沿的是个采样点,在数据线上面是稳定的,取01.在下一个边沿的时候,红色,转换了数据。
数据线上面有交叉就是证明在变化,不能取值,不稳定
数据帧宽度由控制寄存器 SPI_CR1 的 WIDTH 位域配置,可设置 4 ~ 16bit 任意数据位宽。
这个一会儿具体看寄存器,反正就是说,一个数据帧到底是几位的,我们平时使用一个字节,8位。
STM32里面就写了16位,8位,没有这么灵活
MSP的是可以任意编码的
数据的大小端由控制寄存器 SPI_CR1 的 LSBF 位域配置,可选择最高有效位在前(MSB)或最低有效位在前(LSB)。
主机模式帧间隔由控制寄存器 SPI_CR1 的 GAP 位域配置,可选择 0~15 个 SCK 时钟周期间隔。
时钟极性 CPOL,指设备处于没有数据传输的空闲状态时,SCK 串行时钟线的电平状态。
一种有四种
先看时钟,假如说0,那工作的时候第一个边沿就是上升的,这个时候就可以采样或者下一个边沿采样,就是下降沿,非常的简单,时刻记住稳定采样的时刻。
ADI的图很漂亮
我喜欢ST的这个设计,就是很直观
全双工模式:
主机通过 MOSI 发送数据,同时通过 MISO 接收数据。
数据是以位为单位同时交换的,例如主机发送一个字节的同时,从机也返回一个字节。
设置 SPI_CR1.MSTR 为 1,SPI 工作于主机模式。主动发送时钟信号。
主机设置 SPI_SSI.SSI 为 0,在从机选择 CS 引脚输出低电平,作为通信起始信号。 开始的标志是,CS拉低,各单位注意。
当发送缓冲器为空时,即 SPI_ISR.TXE 标志位为 1,将待发送的一帧数据写入 SPI_DR 寄存器,数据在同步移位时钟信号的控制下,从 MOSI 引脚输出,同时将 MISO 引脚的数据接收到移位寄存器。
主机发送现在里面什么东西也没有,这个状态标志为1.这个时候要把发送的数据写进去,接着在时钟的同步下,传输出去。同时接收缓冲区把从机的东西取回来。
发送一个数据帧结束时, 接收缓冲器非空标志位 SPI_ISR.RXNE 由硬件置 1,表示已经接收完成一帧数据,此时可以读取 SPI_DR 寄存器。
设置 SPI_SSI.SSI 为 1,从机选择引脚 CS 输出高电平,结束本次通信。
引脚拉低,一帧完事。
我们想想?如何保证一帧数据的发送完整?
一是CS引脚,同步标志出现
二是时钟引脚上面的采样边沿
CS和SCK是两个重要的信号。
DR是写入数据,其实就是准备,你不是要发送吗?
在MOSI之前准备好这个寄存器的内容。
是说明缓冲区的有无内容
同样,接收的缓存区也可以有状态,好东西
再看一个从机收发
设置 SPI_CR1.MSTR 为 0,SPI 工作于从机模式。
在从机选择 CS 信号被拉低之前,从机需要设置 SPI_ICR.FLUSH 为 0,以清空发送缓冲区和移位寄存器,并将待发送的第一帧数据写入 SPI_DR 寄存器。
当 CS 信号被拉低后,被写入的数据将在主机的同步移位时钟信号的控制下,从 MISO 引脚输出,同时接收 MOSI 引脚的数据到移位寄存器。
如果是多数据帧连续通信,用户应当不断查询 SPI_ISR.TXE 标志位,一旦标志为 1,立即将待发送的数据写入 SPI_DR 寄存器,以免出现数据漏发。
一旦空了,就把数据写进去。缓冲区的作用是,确定里面空了才能发
当接收缓冲器非空时,即 SPI_ISR.RXNE 标志位为 1,表示已经接收完成一帧数据,此时可以读取 SPI_DR 寄存器。当检测到 CS 引脚变为高电平时,本次通信结束。
可以看到就一个线,双向的
单线半双工通信时,由于主机和从机的通信方向无法保证同步切换 ,有可能会出现驱动冲突而导致器件损害,建议在主机和从机之间的数据线上串联一个电阻,以限制电流。
当 SPI 作为从机时,使用单线半双工模式,可以实现与一个标准的 UART 收发器进行同步模式通信,此时 SPI 必须配置为固定的电平模式:时钟极性 CPOL 为 1,时钟相位 CPHA 为 1。
主机发送
设置 SPI_CR1.MSTR 为 1,SPI 工作于主机模式;设置 SPI_CR3.HDOE 为 1,主机仅发送数据。
1就是仅发送
主机设置 SPI_SSI.SSI 为 0,在从机选择引脚 CS 输出低电平,作为通信起始信号。
这些就是搞启动信号,头大
当发送缓冲器为空时,即 SPI_ISR.TXE 标志位为 1,将待发送的一帧数据写入 SPI_DR 寄存器,数据在同步移位 时钟信号的控制下从 MOSI 引脚输出。
当写入最后一帧数据后,必须等待发送缓冲器为空,即 SPI_ISR.TXE 标志位变为 1,同时 SPI_ISR.BUSY 标志位变 为 0,以确保数据发送完毕。
然后设置 SPI_SSI.SSI 为 1,在从机选择 CS 引脚输出高电平,结束本次通信。
没意思。。。。
下面看看STM32C0一个片子:
两个线就行
两个线就可以通讯了
可以看到ST的基本上全系都有DMA,TX是内存到外设
SPI 控制器支持 8 个中断源,当 SPI 中断触发事件发生时,中断标志位会被硬件置位,如果设置了对应的中断使能控制位,将产生中断请求。
呦西,有了中断
开启了中断以后,其实就是大中断,具体是什么中断,还得看寄存器的信息。
在用户 SPI 中断服务程序中,应查询相关 SPI 中断标志位,以进行相应的处理,在退出中断服务程序之前,要清除该中断标志位,避免重复进入中断程序。
很简单的使用
老规矩就是读状态,写数据。
中断方式复杂一点,因为要清楚以及要对缓冲区做处理
走!看看STM32的。
中断在此
就是要判断哪个中断
然后写个回调函数。
如果是双工完成:
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1) // 确保是 SPI1
{
// 处理接收的数据(rx_buffer 中为接收到的数据)
for (int i = 0; i < sizeof(rx_buffer); i++)
{
printf("Received Data[%d]: 0x%02X\n", i, rx_buffer[i]);
}
// 此处可以根据需要继续进行下一次通信
}
}
仅发送数据:
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
printf("SPI Transmit Completed!\n");
}
}
但是需要在NVIC里面:
void HAL_MspInit(void)
{
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
// 配置 SPI1 的中断优先级
HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
}
啊这就是这么多了。
但是TI的单片机更好玩:
TI这个为了提高性能,加了不少外设,主要是FIFO来缓存和拼接。时钟源很丰富。以及有了DMA,可以发送和接收,在完成后也有提醒。
好看
然后我们来具体的说说例子:
同时收发数据通常出现在主设备(Master)在向从设备(Slave)发送某些命令或数据的同时,从设备也将状态或数据返回给主设备。
主设备发送“读取命令”(通常为一个字节,比如 0x03)。 主设备继续发送目标内存地址(通常是 3 个字节,比如 0x001234)。 同时,从设备在接收命令和地址时会返回无意义的占位数据(Dummy Data)。 地址发送完毕后,从设备会根据主设备生成的时钟信号,将存储器中指定地址的数据返回。
假设需要读取地址 0x001234
的数据:
主设备发送: 0x03 0x00 0x12 0x34 0xFF 0xFF 0xFF ...
从设备返回: 0xXX 0xXX 0xXX 0xXX 0x45 0x67 0x89 ...
0x03 是读取命令。
0x00 0x12 0x34 是要读取的地址。
0xFF 是主设备发送的占位数据,用于驱动时钟。
0x45 0x67 0x89 是从设备返回的有效数据。注意看是从设备给的
再看一个
主设备发送读取命令(如 0x51,表示读取数据块)。 主设备发送目标数据块地址。 主设备发送若干字节的占位数据。 SD 卡在接收命令和地址时会返回响应信息,并在后续时钟周期返回数据块内容。
主设备发送: 0x51 0x00 0x00 0x01 0x00 0xFF 0xFF 0xFF ...
从设备返回: 0xFF 0x00 0x00 0x00 0x00 0xFE 0x01 0x02 ...
0x51 是读取命令。 0x00 0x00 0x01 0x00 是数据块地址。 0xFF 是主设备发送的占位数据,用于产生时钟。 0xFE 是 SD 卡的数据起始标志,后续是实际数据内容(0x01 0x02 ...)。
后面橙色的就是从机发出来的,不过主机也得胡诌点
从机第一次传过来的数据是没用的:
因为从机不知道要什么
现在看,是不是很清晰了
这里拿一个SPI的ADC
标准的SPI接口
我喜欢这个名字
这个是时序数据
在CLK的上沿采集
MSB在前,8bit一个字节,帧字走的。
然后看IN和OUT,就是循环的传输的
32bit,4个字节才能取完一次转换的数据
我突然顿悟数据如何发送和接收:
当CS一拉低,从机开始准备传输,当主机的时钟一来的时候,主机内部会一位一位的判断传来的数据是0还是1,这个数据就像发牌一样传到主机的内部寄存器里面,这个寄存器有多大?就是我们提前设置的,然后一个中断发出,CPU或者DMA快速的把数据搬走放到内存,接着下一个继续,字节数据就这样被源源不断的传进去。或者传出去。
现在是ADC的视角,主机发来的数据就是DIN,这个字节就是控制用。然后你看DOUT的数据也会发,但是没关系的。
然后下个字节,DOUT,ADC输出数据给主机。
就这么多了,散会。
https://zipcpu.com/blog/2019/03/27/qflexpress.html
https://dev.ti.com/tirex/explore/node?node=A__ARqjcycmyhag6aCy3n3F4g__MSPM0-ACADEMY-CN__Gx9d9qT__LATEST
https://www.analog.com/cn/resources/analog-dialogue/articles/introduction-to-spi-interface.html
https://www.ti.com/content/dam/videos/external-videos/en-us/10/3816841626001/mspm0-spi-intro.mp4/subassets/mspm0-spi-module-presentation.pdf
https://www.whxy.com/uploads/files/20241016/CW32L010_UserManual_CN_V1.1.pdf
https://www.wpgdadatong.com.cn/blog/detail/45539