欢迎光临散文网 会员登陆 & 注册

SPI通讯主从机程序实现(软件模拟和硬件)

2023-03-26 00:02 作者:gzb2014  | 我要投稿

                                              前言

    写这篇文章算是抛砖引玉,希望有更多人来写,虽然可能理解不到位,但是如果能多些人一起讨论,形成氛围是有好处的,工程人员是国家的刚需,哪个行业不行了理工科总有口饭吃,相应的代价就是枯燥、烦躁,聪明人不一定坐得住,能多一个算一个吧。

    关于后面的实现,我使用的是SN8F5702--主机--模拟/软件SPI、SN8F5909芯片--从机--硬件SPI,其他的实现方式可能有芯片特性的区别,不一定适用。   

                                         基础知识

    SPI基础知识:常见4条线,MOSI、MISO、STE、CLK这4个,M是master主机,S是Slave从机,I是Input,O是Out,主机的输出就是从机的输入方向。SS是片选信号,还有STE,SSN的叫法,选择你通讯的对象,一般信号拉低时选择器件,拉高时SPI不通讯。CLK-clock,和IIC一样用这个信号来做到数据同步。SPI占用IO口多,速度也是最快的,它的限制是看你使用器件中最低的那一个,就像CPU的频率可以很快,但是他的外设达不到那个速度往往要分频降频来输入外设。
                                     

                                        硬件部分

    关于程序实现的思路和需求,我们从硬件讲起,做单片机的都是先硬件后软件,千万不要因为不擅长怕硬件就不去想,这样容易踩坑和走弯路,软件设计基于硬件之上,很多时候你发现效果不一样是忽略了硬件、手册的结果,就像用家电不看厂家手册一样,都是坏习惯。

    硬件需求:我测试过的实现方式是SN8F5702是主机,SN8F5909做从机,主机GPIO口配置MOSI、CLK、SS推挽输出,MISO输入,从机就反过来MOSI、CLK、SS输入,MISO输出。

    我上面这个配置是因为主从机用同系列CPU来做的,电气特性都一致所以也可以,后来仔细想可以配的更好,如果是2个不同型号的CPU,用主机推挽输出不变,输入用内置上拉;从机开漏输出,浮空输入最好,这样做的好处是统一让主机提供电平,如果多个从机设备每个都推挽,那单片机可能会有各自的电平,会稍微影响稳定性。

    用开漏输出加个上拉电阻应该也可以,开漏简单说就是只能IO输出低电平,高电平时本身不输出,需要外接电源上拉电阻来供电,推挽开漏不懂可以手册去具体了解,看看IO口实现的内部电路实现,每款CPU手册都有IO的硬件实现方式,没有考虑过这个问题还可以看看高电平、低电平的定义(比如0.3VDD以下为低,0.7VDD以上为高),然后在延伸了解下TTL RS232、RS485的电平特性(这是UART的),对电平的了解会进一步增长。


                                            软件部分

   程序是主机软件/模拟SPI,从机硬件SPI分别实现。

   关于硬件xx的说法:硬件IIC、SPI是软件实现的,明明是要写程序的却叫硬件xx,因为从硬件上说这是厂家对CPU做好了集成电路,然后软件让用户通过特定寄存器来操作,所以这部分归纳在软件中,硬件xx是一种基于实现特性的叫法,相对的用户自己写流程就是软件/模拟xx,如软件SPI、软件IIC。

     硬件xx和软件/模拟xx的优劣,两者性能上肯定是硬件强,但是硬件方式你如果用了不同系列/CPU,这部分就不可移植要重做 ,而软件方式反正都是GPIO做的,只要做好了移过去改改名字就能用,移植容易,至于使用哪种就看你的需求。

    设计方式:轮询方式实现主从通讯是不用中断,让CPU来判断。中断方式的做法是不占用主程,设定进入中断的条件来打断原先流程。

    配置部分:软件xx配置相应GPIO的输入输出、上下拉、模式,前面硬件有写。

    硬件SPI具体要看手册,下面的一般都要有:

   1.SPI模块和其他中断标志位等的使能(上面设计方式的区别,所以基本都用中断)

    2.MSB和LSB:高位先出还是低位先出

    3.数据个数:一次传输8bit还是16bit

    4.CPOL(Clock polarity)和CPHA(Clock phase):CPOL是时钟极性,就是空闲状态的电平是高还是低,CPHA是时钟相位,第1、还是第2个沿--捕获/采样/锁存/有效数据,附图(松瀚SN8F系列):

    理解及自己画时序图:高低电平空闲很容易理解,分别画2个clk,低-高-低和高-低-高,起始和最终状态就是初始电平。极性是采样的数据,把前面画好的clk*8个,上升下降沿画x代表电平变化,然后在中间划虚线代表采样时刻,先画有效电平后clk也行,数据采样的关键就是采样时刻要保持稳定,所以数据采样前半个clk准备好当前数据,后半clk准备下一个数据,SPI有双线收发,所以主从机可以同一时刻发收,软件实现的会慢一步,而硬件上升下降的速度快很多。

    试着在不看上面的分析和时序图自己捋出来,这样映像会深很多,还有电气特性是硬件SPI的数据。

    数据读写时机:关于发送读取时机我建议在电平前,发送的数据先准备好肯定没问题,读的时机要测试,有的芯片是会在发收完成后改回前面开始的状态的,所以也是放在之前应该也比较好,这个放在前面中间不要插入别的部分。

   程序(整个工程后面链接发,这里只关注主机的软件模拟部分):

相关定义

/********************SPI片选信号********************/

#define SPI_SSN_HIGH P03 = 1//片选拉高

#define SPI_SSN_LOW P03 = 0//片选拉低

/********************SPI时钟信号********************/

#define SPI_SCK_HIGH P13 = 1//时钟拉高

#define SPI_SCK_LOW P13 = 0//时钟拉低

/********************SPI MOSI数据********************/

#define SPI_MOSI_HIGH P14 = 1//MOSI数据拉高

#define SPI_MOSI_LOW P14 = 0//MOSI数据拉低

/********************SPI MISO数据********************/

#define SPI_MISO P15 //数据读取

CPOL=0,CPHA=0,发送模块

uint8_t SPISendRev(uint8_t SpiSendDate)

{

         uint8_t i,j;

         SPI_SSN_LOW;

        

        SPI_SCK_LOW; //初始电平,确保数据发送时时钟是低电平

         for(i=0;i<8;i++)

         {

                SPI_SCK_LOW;

                 if(SpiSendDate & 0x80) //输出数据转bit

                SPI_MOSI_HIGH; //数据发送

                 else

                 SPI_MOSI_LOW;

                SpiSendDate <<= 1; //输出后数据改变

                

                SpiRecDat <<= 1; //接收数据后改变,8位数据7次移动

                if(SPI_MISO == 1) //数据接收

                SpiRecDat++;

                SPI_SCK_HIGH; //拉高SCK信号触发从机发送

         }

        SPI_SCK_LOW;

        

         SPI_SSN_HIGH;

         return SpiRecDat;

}

CPOL=0,CPHA=1,发送模块

uint8_t SPISendRev(uint8_t SpiSendDate)

{

         uint8_t i,j;

         SPI_SSN_LOW;

        

         SPI_SCK_LOW; //初始电平,确保数据发送时时钟是低电平

                 for(i=0;i<8;i++)

                 {

                 SPI_SCK_LOW;

                 if(SpiSendDate & 0x80) //输出数据转bit

                 SPI_MOSI_HIGH; //数据发送

                 else

                 SPI_MOSI_LOW;

                 SpiSendDate <<= 1; //输出后数据改变

                 SPI_SCK_HIGH;

                

                 SpiRecDat <<= 1; //接收数据后改变,8位数据7次移动

                 if(SPI_MISO == 1) //数据接收

                 SpiRecDat++;

                 }

         SPI_SCK_LOW;

        

         SPI_SSN_HIGH;

         return SpiRecDat;

}

CPOL=1,CPHA=0,发送模块

uint8_t SPISendRev(uint8_t SpiSendDate)

{

         uint8_t i;

         SPI_SSN_LOW;

        

         SPI_SCK_HIGH; //初始电平,确保数据发送时时钟是低电平

                 for(i=0;i<8;i++)

                 {

                 SPI_SCK_HIGH; //拉高SCK信号触发从机发送

                 if(SpiSendDate & 0x80) //输出数据转bit

                 SPI_MOSI_HIGH; //数据发送

                 else

                 SPI_MOSI_LOW;

                 SpiSendDate <<= 1; //输出后数据改变

                

                 SpiRecDat <<= 1; //接收数据后改变,8位数据7次移动

                 if(SPI_MISO == 1) //数据接收

                 SpiRecDat++;

                 SPI_SCK_LOW;

         }

         SPI_SCK_HIGH;

        

         SPI_SSN_HIGH;

         return SpiRecDat;

}

CPOL=1,CPHA=1,发送模块

uint8_t SPISendRev(uint8_t SpiSendDate)

{

         uint8_t i;

        SPI_SSN_LOW;

        

         SPI_SCK_HIGH; //初始电平,确保数据发送时时钟是低电平

                 for(i=0;i<8;i++)

                 {

                 if(SpiSendDate & 0x80) //输出数据转bit

                 SPI_MOSI_HIGH; //数据发送

                 else

                 SPI_MOSI_LOW;

                 SpiSendDate <<= 1; //输出后数据改变

                 SPI_SCK_LOW; //拉高SCK信号触发从机发送

                

                 SpiRecDat <<= 1; //接收数据后改变,8位数据7次移动

                 if(SPI_MISO == 1) //数据接收

                 SpiRecDat++;

                 SPI_SCK_HIGH;

                 }

         SPI_SCK_HIGH;

        

        SPI_SSN_HIGH;

         return SpiRecDat;

}

    画好了时序图后就是GPIO模拟的程序写法,如上是CPOL=0和CPHA=0,需要特别关注CPHA=0--第一个沿采样,因为按前面说的发送数据是要等clk到来的,第一个沿却要先采集数据而clk还没来,怎么整?答案是从机在SSN片选信号下降沿之后一段时间就自己发送了。

    该配置下主机第一CLK上升沿发收第1个数据,在第一CLK下降沿发第2个数据,后面每个CLK下降沿都发数据,因此实际在第七个CLK下降沿就发完了数据,第八个CLK上升沿从机接收完成。流程如下:

主机控制SSN拉低-从机MISO发数据-主机MOSI发数据-主机采样-主机CLK上升沿-从机采样-CLK拉低电平-进入下一流程主机MOSI发第二个数据

    上面这个流程有人会发现,我写的主机没有等CLK上升沿到来就发收数据,然后CLK上升沿从机接收,只看主机就是上升沿后立即下降沿,CLK高低电平时间长短不一看着难受,这个就还要谈到芯片特性(我不清楚其他芯片是否这样),因为我测试中发现从机的MISO配置初始电平有影响,硬件的SPI会在发MISO数据的时候改变电平,接着再完成所有数据的时候--第七上升沿接收完成时,把电平改回去,我没有发现这个问题就把主机收MISO数据放在上升沿后,结果就是硬件SPI完成收发后把数据改回去,等于最后1bit没收到,表现出来就是根据数据会收发不到数据,假如你发的最后数据和MISO跳变开始前电平一致,这个数据就不会错,这个结论是示波器抓波形和程序改从机MISO初始电平测试的。

    根据下面的测试,第一个CLK1.9us前MOSI开始发第1个数据,后续每个下降沿720ns后发数据,从机的MISO是第一个CLK3.4us前发第1个数据,后续每个下降沿160ns后发数据,SSN和MISO第一个CLK几乎同时跳变,硬件SPI的实现是从机使能SPI就向SPDAT写数据,而不是等SSN信号来了在写,会错位,自己写软件从机的话应该就要等一段时间的自动发送吧,模拟从机的程序需要测试后再说。

    还有我试图做只有3线版本,MOSI、MISO和SSN的,但是只有CPHA=1才能用,是因为需要SSN信号来立即发送MISO吧,该配置会在第八个CLK完整发完后结束流程。

示波器测试:

黄色CLK,紫色MISO

黄色CLK,紫色MOSI

黄色CLK,紫色SSN


    怎么知道自己对外设的了解程度:先弄个纯软件了解流程,然后用CPU自带的硬件再配置实现,就可以说完美的了解这部分,对多个CPU反复实现这个过程增加了解,最后上升的协议本身,当你从手册写的各种硬件、电气特性到软件程序你都应对自如,你就通关了。比如IIC三种模式的速度限制是100k,400k,3.4M是已经确定的,如果你去面试时说出你的IIC是10M、20M人家就会一脸无语。


    软件和硬件实现的坑:软件/模拟xx因为流程可控,所以如果程序还有别的流程的话影响不小,要以测量为主--考虑程序框架、中断、其他影响问题并实测才能有具体结论,每个程序的目标和实现效果不一样,不可以想当然泛泛而谈,当你打算用其他人现成程序的时候,就要做好哪里出毛病的心里准备,因为其他人做的程序都是固定好了硬件、软件条件,发出来一般只会提到主要部分,中间觉得不重要的、忘记的过程就没有说,所以最好只做参考,特别是不要忽略硬件特性。

    硬件xx中每个厂家的硬件实现、命名方式不一样,它的CPU手册会自带相关说明,框架图,操作方式,原理,寄存器,说到底用人家的制作就要受制于人,听人安排看人手册,注意使用的细节,漏细节的时候就要准备头疼好几天了,几幅图一堆文字、还有细节,要注意的实在太多,卡住的时候最好重新审视一下整体,细节问题还是认识有错误,不会两个都正确最后是流程问题吧?这个情况最好能联络原厂或者懂的人沟通一下。

                                        其他

实现过程中个人遇见的错误及原因分析

比起SPI的问题,我在调试过程中其他的情况反而比较多,费了挺久的。

1.SPI的使能放在GPIO后面,影响了波形

2.GPIO没等初始化的上拉完成就检测,限制条件没有起作用,对于写的程序没有去验证效果

3.SPI的数据写入放在SSN信号后面,导致数据写入不及时

从上面这些可以看出,我对GPIO缺少了解,GPIO就算配置了上拉也要有时间才能达到,2~7us内还在低电平,7~15s高电平到稳定,而我配置完就直接判断,因此需要增加延时等电平波动,还有SPI配置在GPIO之前,那多出了CLK就会影响数据发送。

    我思考了一下一些SPI常见错误,SPI数据不正确,最后一位错位移位的,找不到规律,收到了自己发送的数据的情况,整理一下也许有人需要吧。

1.数据错位且无规律:没有写入数据或写入的时间不对

2.数据错1位:流程写错或写入数据的时间不对,芯片特性,就像我上面也有SPI发完数据返回发送前电平的芯片特性问题。

3.主机收到ff或00:从机没有响应--写入数据

4.收到自己发的数据:我是从机SPI使能,我的除了从机外设使能其他屏蔽了就是这个效果,没有向从机写入数据,那就要看GPIO的电平设置或者中间的改动了


 SPI和IIC、UART的区别:这三者都属于串行通讯,一位一位的移数据,区别就是几条线,性质是数据还是时钟线?SPI是2数据线1时钟1片选,UART是2数据,这两否认数据都是一出一入,可以同时完成收发所以是全双工,IIC是1时钟1数据,一时间内不限定方向所以是半双工。SPI和UART都是一主多从,IIC是多主多从,这是分析软硬件的时结果。

    当你分不清、记不住上面那些的时候,想想他们的软硬件区别,物理层的IO口,电气特性,软件实现的协议部分,程序流程:IO信号的输入输出及电平->时序图->程序,然后你能就很快的想起。


                                            结尾

    个人在黑厂做了几年工作,一直处于一个想学习但又害怕没有结果的茫然,最后通勤路上回到家就什么都不想干了的状态,知道方向想努力却又很模糊,掰了之后重新找工作的时候太菜被嫌弃了,就有种梦中惊醒的感觉,还好年轻可以努力,每隔一段时间应该会发一下技术学习心得来鞭策自己,状态允许的话尽量每天看文档学英语,各种模拟电子、硬件电路什么的也要补……

    以上,理工科的切记:多去动手,实验室也好,自己买一些元器件、开发板、示波器也好,光看视频是没有长进的,这样做会让你在面试、工作的时候印象模糊和支支吾吾,想到了问题就去动手测试,然后多交流。只要肯动手去做就会前途大好,你我共勉!


程序链接:https://pan.baidu.com/s/19u8EiytQOib9g-uVSg3sNw?pwd=trz5 

提取码:trz5

注意:程序有些注释对不上,还有CPOL=1和CPHA=1在测试中改过,下面的屏蔽去掉

//SPI_SSN_LOW;

//SPI_SSN_HIGH;

return SpiRecDat;

SPI通讯主从机程序实现(软件模拟和硬件)的评论 (共 条)

分享到微博请遵守国家法律