STM32F1的SPI模块协议介绍

spi是是一种高速的,全双工,同步的总线通信方式。stm32f1低中容量设备的spi模块支持主从两种模式。
一、spi协议介绍
1.硬件连接
spi使用三条数据总线和一条片选线: mosi、miso、sck、nss(cs)
mosi(sdo):主设备输出/从设备输入。用于将数据从主机输出到从机。
miso(sdi):主设备输入/从设备输出。数据经此由从机至主机,主机接收数据。
sck:时钟信号线,用于通讯同步。时钟信号由主机提供
nss:片选信号线。由主机通过此线使能从机。在一主多从的通讯模式下,只能同时有一个从机被使能。
spi器件间的连接很简单,如图,只要名字相同线相连即可,主也可以比较方便地反转。
2.通信时序
spi传输模式的精髓在时钟极性(cpol)和时钟相位(cpha)
cpol控制空闲状态下时钟总线sck的电平:
cpol=0(low);时钟线空闲为低电平
cpol=1(high);时钟线空闲为高
cpha控制采样位置和信号跳变位置:
※spi传输从高位(msb)开始还是低位(lsb)开始可以由用户设置
二、stm32f1的spi模块
1.在cubemx中进行配置
数据位可选择8或16
cpha可选择从第一个数据沿开始(cpha=0)或从第二个数据沿开始(cpha=1)
可选择高位先发送或是低位先发送
可选择是否使用nss以及nss功能(输入、输出用)。用户也可以用普通io的中断输入/输出功能模拟nss,这种方法相对更加灵活。
※ 按照个人开发经验,spi一般配置为双线双向全双工情况比较多。
※stm32的 nss引脚说明和工作极其复杂,建议开发者禁用硬件nss,自行定义普通gpio实现片选功能。(nss脚禁用后可以进行gpio配置当作普通io控制实现cs功能)
2.相关寄存器
api:
1.初始化结构体ll_spi_inittypedef
typedef struct{ uint32_t transferdirection;/* 数据线配置;通过调用ll_spi_settransferdirection()实现; @ref: ll_spi_full_duplex //全双工,双线双向 ll_spi_simplex_rx //双线双向模式下禁止输出,仅能输入 ll_spi_half_duplex_rx //单线,仅能接收 ll_spi_half_duplex_tx //单线,仅能发送 ※单线模式下,工作于master时使用mosi脚;slave时为miso脚 */ uint32_t mode;/* 设置主从模式,通过ll_spi_setmode()实现; @ref: ll_spi_mode_master //主模式,配置时若nss由软件管理会将电平置高 ll_spi_mode_slave */ uint32_t datawidth;/* 设置数据长度;通过ll_spi_setdatawidth()实现; @ref: ll_spi_datawidth_8bit //8位 ll_spi_datawidth_16bit //16位 */ uint32_t clockpolarity;/* 设置时钟极性(cpol),通过ll_spi_setclockpolarity()实现 @ref: ll_spi_polarity_low //低电平(cpol=0) ll_spi_polarity_high //高电平(cpol=1) */ uint32_t clockphase;/* 设置时钟相位,通过ll_spi_setclockphase()实现 @ref: ll_spi_phase_1edge //cpha =0 ll_spi_phase_2edge //cpha=1 */ uint32_t nss;/* 配置nss(cs),通过ll_spi_setnssmode()实现; @ref: ll_spi_nss_soft //通过软件管理nss;※此时nss引脚无法进行i/o操作控制 //cubemx配置为disable时配置为此模式(相当于禁用了nss) //此时可以通过操作spi_cr1- >ssi位控制该位电平;ll库未提供函数; ll_spi_nss_hard_input //说不清除,手册和库函数说明冲突,建议不用 ll_spi_nss_hard_output//同样,不建议配置 //鉴于片选复杂性,推荐开发者直接通过gpio直接模拟nss(cs)功能,可用原nss */ uint32_t baudrate;/* 配置波特率分频,通过ll_spi_setbaudrateprescaler()实现; @ref: ll_spi_baudrateprescaler_divx //x为2^n,max=128*/ uint32_t bitorder;/* 配置发送位顺序,通过ll_spi_settransferbitorder()实现; @ref: ll_spi_lsb_first //低位先 ll_spi_msb_first //高位先 */ uint32_t crccalculation;/*!< specifies if the crc calculation is enabled or not. this parameter can be a value of @ref spi_ll_ec_crc_calculation.this feature can be modified afterwards using unitary functions @ref ll_spi_enablecrc() and @ref ll_spi_disablecrc().*/ uint32_t crcpoly;/*!< specifies the polynomial used for the crc calculation.this parameter must be a number between min_data = 0x00 and max_data = 0xffff. this feature can be modified afterwards using unitary function @ref ll_spi_setcrcpolynomial().*/} ll_spi_inittypedef;2.初始化函数
errorstatus ll_spi_init(spi_typedef *spix, ll_spi_inittypedef *spi_initstruct);/*初始化spi;*/void ll_spi_structinit(ll_spi_inittypedef *spi_initstruct)/*初始化spi配置结构体*/errorstatus ll_spi_deinit(spi_typedef *spix)/*初始化spi模块*/3.开启/关闭模块
__static_inline void ll_spi_enable(spi_typedef *spix);/*开启spi模块*/__static_inline void ll_spi_disable(spi_typedef *spix);/*关闭spi模块*/__static_inline uint32_t ll_spi_isenabled(spi_typedef *spix);/*检测开启状态*/※与uart不同,目前版本cubemx自动生成代码不会开启spi,需用户手动开启
关闭spi需要在传输完成后
4.标志位/状态位
modf:主模式失效错误标志。在nss引脚硬件模式管理下,主设备的nss脚被拉低时;或者在nss引脚软件模式管理下,ssi位被置0时被置位。同时spi模块被关闭。 在使用ll库时若不使用nss功能,则不会出现置位情况
※rxne:接收缓冲非空。与usart类似,当※接收数据寄存器完全完成一次数据接收时,该位被置位。※对读取数据寄存器rdr的读取操作可以硬件清零该位。
※txe:发送缓冲空。当发送数据寄存器数据被送出时,该位被置位。对发送数据寄存器tdr的写入操作可以硬件清零该位。
bsy:忙标志。spi在通讯时该位为1。该位完全由硬件控制。在主模式的双向接收模式下 (mstr=1、bdm=1并且bdoe=0),在接收期间bsy标志保持为低。不要使用bsy标志处理每一个数据项的发送和接收,最好使用txe和rxne标志。
ovr:溢出错误。接收数据时,当发送端设备已经发送了数据字节,而stm32还没有清除前一个数据字节产生的rxne时,即为溢出错误。
当溢出时,读spi_dr寄存器返回的是之前未读的数据,所有随后传送的数据都被丢弃。
※与usart不同,spi模块txe与rxne位是只读的,其值由硬件管理。
__static_inline uint32_t ll_spi_isactiveflag_bsy(spi_typedef *spix);/*检测bsy是否置位,该位无法软件控制*/__static_inline uint32_t ll_spi_isactiveflag_ovr(spi_typedef *spix);/*检测ovr是否置位(发生过载错误)*/__static_inline void ll_spi_clearflag_ovr(spi_typedef *spix);/*置位ovr*/__static_inline uint32_t ll_spi_isactiveflag_txe(spi_typedef *spix);/*检测txe是否置位*/__static_inline uint32_t ll_spi_isactiveflag_rxne(spi_typedef *spix);/*检测rxne是否置位*/__static_inline uint32_t ll_spi_isactiveflag_modf(spi_typedef *spix);__static_inline void ll_spi_clearflag_modf(spi_typedef *spix);5.中断控制
__static_inline void ll_spi_enableit_err(spi_typedef *spix);/*使能err错误中断*/__static_inline void ll_spi_disableit_err(spi_typedef *spix);/*禁用err错误中断*/__static_inline uint32_t ll_spi_isenabledit_err(spi_typedef *spix);/*检测是否开启err中断*/__static_inline void ll_spi_enableit_rxne(spi_typedef *spix);/*使能rxne接收缓冲非空中断*/__static_inline void ll_spi_disableit_rxne(spi_typedef *spix);/*禁用rxne接收缓冲非空中断*/__static_inline uint32_t ll_spi_isenabledit_rxne(spi_typedef *spix)/*检测是否开启rxne接收缓冲非空中断*/__static_inline void ll_spi_enableit_txe(spi_typedef *spix);/*使能txe发送缓冲空中断*/__static_inline void ll_spi_disableit_txe(spi_typedef *spix);/*禁用txe发送缓冲空中断*/__static_inline uint32_t ll_spi_isenabledit_txe(spi_typedef *spix)/*检测是否开启txe发送缓冲空中断*/6.spi 收/发函数
__static_inline uint8_t ll_spi_receivedata8(spi_typedef *spix);/*从接收寄存器(缓冲区)dr中读取8位数据;*/__static_inline uint16_t ll_spi_receivedata16(spi_typedef *spix);/*从接收寄存器(缓冲区)dr中读取16位数据;*/__static_inline void ll_spi_transmitdata8(spi_typedef *spix, uint8_t txdata);/*向发送寄存器(缓冲区)dr中写入8位数据*/__static_inline void ll_spi_transmitdata16(spi_typedef *spix, uint16_t txdata);/*向发送寄存器(缓冲区)dr中写入16位数据*/spi模块dma的使用
相关函数:
待实验
__static_inline void ll_usart_enabledmareq_rx(usart_typedef *spix);/*使能接收dma,启用后dr有数据时将允许发送dma请求;具体见示例用法*/__static_inline void ll_usart_disabledmareq_rx(usart_typedef *spix);/*禁用接收dma*/__static_inline uint32_t ll_usart_isenableddmareq_rx(usart_typedef *spix);/*检测是否使能接收dma*/__static_inline void ll_usart_enabledmareq_tx(usart_typedef *spix);/*使能发送dma*/__static_inline void ll_usart_disabledmareq_tx(usart_typedef *spix);/*禁用发送dma*/__static_inline uint32_t ll_usart_isenableddmareq_tx(usart_typedef *spix);/*检测是否使能发送dma*//**************************************************/__static_inline uint32_t ll_usart_dma_getregaddr(usart_typedef *spix);/*返回spi模块数据寄存器dr地址;无论是否启用dma均可用*/发送时,在每次txe被设置为’1’时发出dma请求,此时软件控制dma写数据至spi_dr寄存器,txe标志因此而被清除。
接收时,在每次rxne被设置为’1’时发出dma请求,在开启情况下dma控制器从spi_dr寄存器读出数据,rxne标志因此而被清除。
ll的dma使用与uart相似,可以参考之前的文章。
spi在双向全双工传输数据的时候,每发出一字节数据的同时也会接收一字节数据,因此在作为主机接收的时候,应当考虑 如何处理接收到的无用数据。否则会出现ovr。
另外,由于在双向模式下配置为主机时,只有当spi在写数据时时钟信号才能产生。处于master工作模式下,spi的时钟只有在往dr寄存器里面写数据的时候才会产生,读是不会产生的。所以要读取slave shift out的数据,master必须先发一个“dummy”数据以产生时钟。
建议配置stm32为双向主机、从机; 配置为主机接收前读取一次dr,再发送dummy(建议发0x00或0xff,不要增加没必要的干扰)

SMP多核启动向内核传递参数信息
数字温度传感器按照不同形式分可分为哪几类
单片机加外壳就是PLC吗?
基础射频实验-射频波形基本测量
热流传感器信号采集
STM32F1的SPI模块协议介绍
女神厉害!王小云院士设计SM3密码,保护6亿智能电网用户上亿银行卡
LG G6 设计外观曝光,不再采用模组化设计
光纤收发器的类型
低代码可视化开发 快速构建工业物联网云平台
基于PBS生物反应器的共享控制平台解决方案
iPhone15全系采用USB-C,订单数量将少于上代
中国移动磐基 PaaS 平台与飞腾 CPU 完成适配,共铸云原生产业生态
手动与自动锡膏印刷机:优缺点全面PK!
电子封装与SMT是平行还是交叉?
MLX90121 RFID收发器与ISO标准的通信技术
自动驾驶呈逐步发展的态势 ams积极投资中国汽车市场
安路科技EF2 FPGA荣获2019中国电子信息博览会“创新产品奖”
位置传感器及编码器关键术语,如何选择合适的位置传感器
电磁、孔板、涡轮、涡街、超声波流量计性能对比