spi协议是由摩托罗拉公司提出的通讯协议(serial peripheral interface),即串行外围设备接口,是一种高速全双工的通信总线。它在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为pcb在布局上节省了空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,它被广泛地使用在adc、lcd、flash等设备与mcu之间的通信。
cks32f4xx系列产品spi介绍
cks32f4xx系列的spi外设可用作通讯的主机及从机,支持最高的sck时钟频率为fpclk/2(cks32f407型号的芯片默认fpclk为142mhz,fpclk2为84mhz),完全支持spi协议的4种模式。spi协议根据cpol及cpha的不同状态分成的四种工作模式如下表所示:
cks32f4xx系列的spi架构如下图所示:
图中的1处是spi的引脚mosi、miso、sck、nss。cks32f4xx芯片有多个 spi外设,它们的spi通讯信号引出到不同gpio引脚上,使用时必须配置到这些指定的引脚。关于gpio引脚的复用功能可以查阅芯片数据手册。各个引脚的作用介绍如下:
(1)nss:从设备选择信号线,常称为片选信号线。当有多个spi从设备与 spi主机相连时,设备的其它信号线sck、mosi及miso同时并联到相同的spi 总线上,即无论有多少个从设备,都共同只使用这3条总线;而每个从设备都有独立的一条nss信号线,当主机要选择从设备时,把该从设备的nss信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行spi通讯。所以spi通讯以nss线置低电平为开始信号,以nss线被拉高作为结束信号。
(2)sck:时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,两个设备之间通讯时,通讯速率受限于低速设备。
(3)mosi:主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
(4)miso:主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
图中的2处是sck线的时钟信号,由波特率发生器根据“控制寄存器cr1”中的br[0:2]位控制,该位是对fpclk时钟的分频因子,对fpclk的分频结果就是sck引脚的输出时钟频率。
图中的3处是spi的数据控制逻辑。spi的mosi及miso都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区及发送缓冲区以及miso、mosi线。当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。通过写spi 的“数据寄存器dr”把数据填充到发送缓冲区中,通过“数据寄存器dr”,可以获取接收缓冲区中的内容。其中数据帧的长度可以通过“控制寄存器cr1”的“dff位”配置成8位及16位模式;配置“lsbfirst位”可选择msb先行还是 lsb先行。
图中的4处是spi的整体控制逻辑。整体控制逻辑负责协调整个spi外设,控制逻辑的工作模式根据我们配置的“控制寄存器(cr1/cr2)”的参数而改变,基本的控制参数包括spi模式、波特率、lsb先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(sr)”,我们只要读取状态寄存器相关的寄存器位,就可以了解spi的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生spi中断信号、dma请求及控制nss信号线。实际应用中,我们一般不使用cks32 spi外设的标准nss信号线,而是更简单地使用普通的gpio,软件控制它的电平输出,从而产生通讯起始和停止信号。
cks32f4xx系列的spi作为通讯主机端时收发数据的过程如下:
(1) 控制nss信号线,产生起始信号;
(2) 把要发送的数据写入到“数据寄存器dr”中,该数据会被存储到发送缓冲区;
(3) 通讯开始,sck时钟开始运行。mosi把发送缓冲区中的数据一位一位地传输出去;miso则把数据一位一位地存储进接收缓冲区中;
(4) 当发送完一帧数据的时候,“状态寄存器sr”中的“txe标志位”会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“rxne标志位”会被置1,表示传输完一帧,接收缓冲区非空;
(5) 等待到“txe标志位”为1时,若还要继续发送数据,则再次往“数据寄存器dr”写入数据即可;等待到“rxne标志位”为1时,通过读取“数据寄存器dr”可以获取接收缓冲区中的内容。
假如我们使能了txe或rxne中断,txe或rxne置1时会产生spi中断信号,进入同一个中断服务函数,到spi中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用dma方式来收发“数据寄存器 dr”中的数据。
cks32f4xx系列产品spi的配置
接下来我们讲解如何利用cks32f4xx系列固件库来完成对spi的配置使用。跟其它外设一样,cks32标准库提供了spi初始化结构体及初始化函数来配置 spi外设。了解初始化结构体后我们就能对spi外设运用自如了,代码如下:
typedef struct{ uint16_t spi_direction; uint16_t spi_mode; uint16_t spi_datasize; uint16_t spi_cpol; uint16_t spi_cpha; uint16_t spi_nss; uint16_t spi_baudrateprescaler; uint16_t spi_firstbit; uint16_t spi_crcpolynomial; }spi_inittypedef;
结构体中各个成员变量的介绍及初始化时可被赋的值如下:
1) spi_direction:本成员设置spi的通讯方向,可设置为双线全双工 (spi_direction_2lines_fullduplex),双线只接收 (spi_direction_2lines_rxonly),单线只接收(spi_direction_1line_rx)、单线只发送模式(spi_direction_1line_tx)。
2) spi_mode:本成员设置spi工作在主机模式(spi_mode_master)或从机模式(spi_mode_slave ),这两个模式的最大区别为spi的sck信号线的时序,sck的时序是由通讯中的主机产生的。若被配置为从机模式,cks32的spi外设将接受外来的sck信号:
3) spi_datasize: 本成员可以选择spi通讯的数据帧大小是为8位 (spi_datasize_8b)还是16位(spi_datasize_16b)。
4) spi_cpol和spi_cpha: 这两个成员配置spi的时钟极性cpol和时钟相位cpha,前面讲过这两个配置影响到spi的通讯模式。时钟极性cpol成员可设置为高电平(spi_cpol_high)或低电平(spi_cpol_low )。时钟相位cpha则 可以设置为spi_cpha_1edge(在sck的奇数边沿采集数据)或 spi_cpha_2edge(在sck的偶数边沿采集数据)。
5) spi_nss: 本成员配置nss引脚的使用模式,可以选择为硬件模式 (spi_nss_hard )与软件模式(spi_nss_soft ),在硬件模式中的spi片选信号由 spi硬件自动产生,而软件模式则需要我们自己把相应的gpio端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
6) spi_baudrateprescaler: 本成员设置波特率分频因子,分频后的时钟即为spi的sck信号线的时钟频率。这个成员参数可设置为fpclk的2、4、6、8、16、32、64、128、256分频。可选的值如下所示:
spi_baudrateprescaler_2 //2分频spi_baudrateprescaler_4 //4分频spi_baudrateprescaler_6 //6分频spi_baudrateprescaler_8 //8分频spi_baudrateprescaler_16 //16分频spi_baudrateprescaler_32 //32分频spi_baudrateprescaler_64 //64分频spi_baudrateprescaler_128 //128分频spi_baudrateprescaler_256 //256分频
7) spi_firstbit: 所有串行的通讯协议都会有msb先行(高位数据在前)还是 lsb先行(低位数据在前)的问题,而cks32f4xx系列的spi模块可以通过这个结构体成员,对这个特性编程控制。
spi_firstbit_msb //高位数据在前spi_firstbit_lsb //低位数据在前
8) spi_crcpolynomial: 这是spi的crc校验中的多项式,若我们使用crc 校验时,就使用这个成员的参数(多项式),来计算crc的值。
配置完这些结构体成员的值,调用库函数spi_init即可把结构体的配置写入到寄存器中。
cks32f4xx读写spi flash实验
串口的dma接发通信实验是存储器到外设和外设到存储器的数据传输。在第24
本小节以一种使用spi通讯的串行flash存储芯片的读写实验为大家讲解 cks32f4xx系列的spi使用方法。实验中的flash芯片(型号:w25q32)是一种使用spi通讯协议的norflash存储器,它的cs/clk/dio/do引脚分别连接到了cks32f4xx对应的spi引脚nss/sck/mosi/miso上,其中cks32f4xx的nss引脚是一个普通的gpio,不是spi的专用nss引脚,所以程序中我们要使用软件控制的方式。
1.编程要点
(1) 初始化通讯使用的目标引脚及端口时钟;
(2) 使能spi外设的时钟;
(3) 配置spi外设的模式、地址、速率等参数并使能spi外设;
(4) 编写基本spi按字节收发的函数;
(5) 编写对flash擦除及读写操作的的函数;
(6) 编写测试程序,对读写数据进行校验。
2.代码分析
代码清单1:w25q32初始化配置
void w25qxx_init(void){ gpio_inittypedef gpio_initstructure; rcc_ahb1periphclockcmd(rcc_ahb1periph_gpiod, enable); rcc_ahb1periphclockcmd(rcc_ahb1periph_gpiog, enable); gpio_initstructure.gpio_pin = gpio_pin_6; gpio_initstructure.gpio_mode = gpio_mode_out; gpio_initstructure.gpio_otype = gpio_otype_pp; gpio_initstructure.gpio_speed = gpio_speed_100mhz; gpio_initstructure.gpio_pupd = gpio_pupd_up; gpio_init(gpiod, gpio_initstructure); gpio_initstructure.gpio_pin = gpio_pin_3; gpio_init(gpiog, gpio_initstructure); gpio_setbits(gpiog,gpio_pin_3); w25qxx_cs=1; //spi flash不选中 spi1_init(); //初始化spi spi1_setspeed(spi_baudrateprescaler_4); //设置为21m时钟 w25qxx_type=w25qxx_readid(); //读取flash id.}
上面的代码主要是完成对w25q32片选引脚的初始化,spi初始化。spi通信速率设置和读取w25q32的id。
代码清单2:spi初始化函数
void spi1_init(void){ gpio_inittypedef gpio_initstructure; spi_inittypedef spi_initstructure; rcc_ahb1periphclockcmd(rcc_ahb1periph_gpiob, enable); rcc_apb2periphclockcmd(rcc_apb2periph_spi1, enable); gpio_initstructure.gpio_pin = gpio_pin_3|gpio_pin_4|gpio_pin_5; gpio_initstructure.gpio_mode = gpio_mode_af; gpio_initstructure.gpio_otype = gpio_otype_pp; gpio_initstructure.gpio_speed = gpio_speed_100mhz; gpio_initstructure.gpio_pupd = gpio_pupd_up; gpio_init(gpiob, gpio_initstructure); gpio_pinafconfig(gpiob,gpio_pinsource3,gpio_af_spi1); gpio_pinafconfig(gpiob,gpio_pinsource4,gpio_af_spi1); gpio_pinafconfig(gpiob,gpio_pinsource5,gpio_af_spi1); rcc_apb2periphresetcmd(rcc_apb2periph_spi1,enable); rcc_apb2periphresetcmd(rcc_apb2periph_spi1,disable); spi_initstructure.spi_direction = spi_direction_2lines_fullduplex; spi_initstructure.spi_mode = spi_mode_master; spi_initstructure.spi_datasize = spi_datasize_8b; spi_initstructure.spi_cpol = spi_cpol_high; spi_initstructure.spi_cpha = spi_cpha_2edge; spi_initstructure.spi_nss = spi_nss_soft; spi_initstructure.spi_baudrateprescaler = spi_baudrateprescaler_256; spi_initstructure.spi_firstbit = spi_firstbit_msb; spi_initstructure.spi_crcpolynomial = 7; spi_init(spi1, spi_initstructure); spi_cmd(spi1, enable); spi1_readwritebyte(0xff); }
上面这段代码主要是完成对spi1的初始化,首先是配置了spi1使用的引脚spi1_sck、spi1_mosi和spi1_miso。然后是根据第2小节的内容完成对spi1外设模式的配置。根据flash芯片w25q32的说明,它支持spi模式0及模式3,支持双线全双工,使用msb先行模式,支持最高通讯时钟为104mhz,数据帧长度为8位。我们要把cks32f4的spi外设中的这些参数配置一致。
代码清单3:spi1单字节收发函数
u8 spi1_readwritebyte(u8 txdata){ while (spi_i2s_getflagstatus(spi1, spi_i2s_flag_txe) == reset){} spi_i2s_senddata(spi1, txdata); while (spi_i2s_getflagstatus(spi1, spi_i2s_flag_rxne) == reset){} return spi_i2s_receivedata(spi1); }
本函数中不包含spi起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作。通过检测txe标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;等待至发送缓冲区为空后,调用库函数spi_i2s_senddata把要发送的数据“txdata”写入到spi的数据寄存器dr,写入spi数据寄存器的数据会存储到发送缓冲区,由spi外设发送出去;写入完毕后等待rxne事件,即接收缓冲区非空事件。由于spi双线全双工模式下mosi与miso数据传输是同步的,当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲区也收到新的数据;等待至接收缓冲区非空时,通过调用库函数spi_i2s_receivedata读取spi的数据寄存器dr,就可以获取接收缓冲区中的新数据了。代码中使用关键字“return”把接收到的这个数据作为spi1_readwritebyte函数的返回值。
搞定了spi的基本收发单元后,还需要了解如何对flash芯片进行读写。flash 芯片自定义了很多指令,我们通过控制cks32f4利用spi总线向flash 芯片发送指令,flash芯片收到后就会执行相应的操作。具体的指令代码可以查看w25q32芯片的数据手册。
代码清单4:读取flash芯片id函数
u16 w25qxx_readid(void){ u16 temp = 0; w25qxx_cs=0; spi1_readwritebyte(0x90); spi1_readwritebyte(0x00); spi1_readwritebyte(0x00); spi1_readwritebyte(0x00); temp|=spi1_readwritebyte(0xff)<>16)); spi1_readwritebyte((u8)((dst_addr)>>8)); spi1_readwritebyte((u8)dst_addr); w25qxx_cs=1; w25qxx_wait_busy(); }
目标扇区被擦除完毕后,就可以向它写入数据了。与eeprom类似,flash芯片也有页写入命令,使用页写入命令最多可以一次向flash传输256个字节的数据,我们把这个单位称为页大小。在进行页写入时第1个字节为“页写入指令”编码,2-4字节为要写入的“地址a”,接着的是要写入的内容,最多可以发送 256字节数据,这些数据将会从“地址a”开始,按顺序写入到flash的存储矩阵。若发送的数据超出256个,则会覆盖前面发送的数据。
代码清单7:w25q32页写入函数
void w25qxx_write_page(u8* pbuffer,u32 writeaddr,u16 numbytetowrite){ u16 i; w25qxx_write_enable(); w25qxx_cs=0; spi1_readwritebyte(w25x_pageprogram); spi1_readwritebyte((u8)((writeaddr)>>16)); spi1_readwritebyte((u8)((writeaddr)>>8)); spi1_readwritebyte((u8)writeaddr); for(i=0;i
应用的时候我们常常要写入不定量的数据,直接调用“页写入”函数并不是特别方便,所以我们页写入函数的基础上编写了“不定量数据写入”的函数。在实际调用这个“不定量数据写入”函数时,还要注意确保目标扇区处于擦除状态
代码清单8:w25q32不定量数据写入函数
void w25qxx_write(u8* pbuffer,u32 writeaddr,u16 numbytetowrite) { u32 secpos; u16 secoff; u16 secremain; u16 i; u8 * w25qxx_buf; w25qxx_buf=w25qxx_buffer; secpos=writeaddr/4096; secoff=writeaddr%4096; secremain=4096-secoff; if(numbytetowrite>16)); spi1_readwritebyte((u8)((readaddr)>>8)); spi1_readwritebyte((u8)readaddr); for(i=0;i
函数的入口参数pbuffer是数据存储区、readaddr是开始读取的地址(24bit)、numbytetoread是要读取的字节数(最大65535)。
完成基本的读写函数后,接下来我们编写一个读写测试函数来检验驱动程。
代码清单10:w25q32读写测试函数
uint8_t w25q32_test(void){ u16 i; printf(写入的数据:rn); for ( i=0; i<=10; i++ ) { spi_buf_write[i] = i; printf(0x%02x , spi_buf_write[i]); } w25qxx_write((u8*)spi_buf_write,flash_size-100,11); printf(写成功,); printf(读出的数据:rn); w25qxx_read(datatemp,flash_size-100,11); for (i=0; i<11; i++) { if(datatemp[i] != spi_buf_write[i]) { printf(0x%02x , datatemp[i]); printf(错误:i2c eeprom写入与读出的数据不一致); return 0; } printf(0x%02x , datatemp[i]); } printf(rn); printf(spi(w25q32)读写测试成功); return 1;}
代码中先填充一个数组,数组的内容为0,1至10,接着把这个数组的内容写入到spi flash中,并将写入的数据打印输出到串口调试助手。写入完毕后再从spi flash的地址中读取数据,把读取到的数据与写入的数据进行校验,若一致说明读写正常,否则读写过程有问题或者spi flash芯片不正常,然后再将读取到的数据打印输出到串口调试助手。
代码清单11:主函数
int main(void){ u16 id = 0; nvic_prioritygroupconfig(nvic_prioritygroup_2); delay_init(168); usart_configuration(); w25qxx_init(); while(1) { id = w25qxx_readid(); if (id == w25q32 || id == nm25q32) break; printf(w25q32 init failedrn); delay_ms(500); delay_ms(500); } printf(w25q32 init successrn); w25q32_test(); while(1) { } }
主函数代码比较简单,主要是完成串口初始化和w25q32的初始化,初始化完成之后会执行w25qxx_readid函数,读取w25q32的id,同时对id进行判断,并将结果通过串口调试助手打印输出。然后会执行一次w25q32测试函数,并将一些测试结果通过串口调试助手打印输出。
来源:中科芯mcu
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理
什么是有源音箱和无源音箱_有源音箱和无源的区别是什么
珠海力士乐叶片泵油封被击穿而串油的原因分析
紫光国微超级汽车芯系列产品助力汽车产业变革
工业互联网平台的技术体系与关键技术
电池修复和维护一样吗?
MCU微课堂 | CKS32F4xx系列产品SPI通信
TB1240AN- -IIC总线控制的TV处理集成电路
元宇宙的概念与应用
聚焦 | 基于深度学习的人工智能技术已触及天花板
Freescale扩展嵌入式电机控制系列提供经济高效的8位M
选气动球阀做为紧急切断阀门时该如何正确地操作
85-015G-XX-10095压力传感器的保温措施是什么?
哪一些策略推动了物联网产业的发展
采集仪器的防护等级介绍
基于UCC28070设计的300W PFC高效电源方案
IP知识百科之AI防火墙
飞航管制结合AR技术迈向虚拟化
为什么要实现无人驾驶落地,告诉你三个理由!
Altera支持硅片融合实现的混合系统架构
为什么笔记本接口越做越少