完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
一、概述
SPI(Serial Peripheral Interface,串行外设接口)是一种全双工同步串行通信接口,它用于MCU与各种外围设备以串行方式进行通信以交换信息,通信速度最高可达25MHz以上,SPI接口主要应用在EEPROM、FLASH、实时时钟、网络控制器、OLED显示驱动器、AD转换器,数字信号处理器、数字信号解码器,温度传感器等设备之间。 如下图,RVB2601板载两个SPI接口,SPI0连接到W800,传输网络信息,SPI1连接到OLED,用于数据显示,理论上来讲,一个SPI总线上可以挂接多个设备,但是如果各设备的时序逻辑、时钟频率不一致,会导致数据传输效率低下、读写错误、甚至死机,所以我借鉴Arduino开发板上的软件模拟SPI方式,基于平头哥RVB2601开发板,通过GPIO连接外设,实现连接一个SPI接口的温度传感器,并读取数据。 二、SPI 时序逻辑(参考STM32软件模拟SPI读写Flash存储器) SPI通常由四条线组成,一条主设备输出与从设备输入(Master Output Slave Input,MOSI),一条主设备输入与从设备输出(Master Input Slave Output,MISO),一条时钟信号(Serial Clock,SCLK),一条从设备使能选择(Chip Select,CS)。与I²C类似,协议都比较简单,也可以使用GPIO模拟SPI时序。 SPI可以一个主机连接单个或多个从机,每个从机都使用一个引脚进行片选,物理连接示意图如图21.1.1 和 图 21.1.2 所示。 数据交换 在SCK时钟周期的驱动下,MOSI和MISO同时进行,如图 21.1.3 所示,可以看作一个虚拟的环形拓扑结构。 主机和从机都有一个移位寄存器,主机移位寄存器数据经过MOSI将数据写入从机的移位寄存器,此时从机移位寄存器的数据也通过MISO传给了主机,实现了两个移位寄存器的数据交换。无论主机还是从机,发送和接收都是同时进行的,如同一个“环”。 如果主机只对从机进行写操作,主机只需忽略接收的从机数据即可。如果主机要读取从机数据,需要主机发送一个空数据来引发从机发送数据。 传输模式 SPI有四种传输模式,如表 21.1.2 所示,主要差别在于CPOL和CPHA的不同。 CPOL(Clock Polarity,时钟极性)表示SCK在空闲时为高电平还是低电平。当CPOL=0,SCK空闲时为低电平,当CPOL=1,SCK空闲时为高电平。 CPHA(Clock Phase,时钟相位)表示SCK在第几个时钟边缘采样数据。当CPHA=0,在SCK第一个边沿采样数据,当CPHA=1,在SCK第二个边沿采样数据。 如图 21.1.4 所示,CPHA=0时,表示在时钟第一个时钟边沿采样数据。当CPOL=1,即空闲时为高电平,从高电平变为低电平,第一个时钟边沿(下降沿)即进行采样。当CPOL=0,即空闲时为低电平,从低电平变为高电平,第一个时钟边沿(上升沿)即进行采样。 如图 21.1.5 所示,CPHA=1时,表示在时钟第二个时钟边沿采样数据。当CPOL=1,即空闲时为高电平,从高电平变为低电平再变为高电平,第二个时钟边沿(上升沿)即进行采样。当CPOL=0,即空闲时为低电平,从低电平变为高电平再变为低电平,第二个时钟边沿(下降沿)即进行采样。 三、传感器时序(参考MAX6675使用笔记) MAX6675是一款精密的热电偶数字转换器,内置12位模数转换器(ADC)。 MAX6675还包括冷端补偿检测和校正,数字控制器,SPI兼容接口以及相关的控制逻辑。MAX6675设计用于在恒温,过程控制或监控应用中与外部微控制器或其他智能器件配合使用。 1.温度转换 MAX6675包括信号调节硬件,可将热电偶的信号转换为与ADC输入通道兼容的电压。T +和T-输入连接到内部电路,可减少热电偶导线引入的噪声误差。 在将热电电压转换为等效温度值之前,必须补偿热电偶冷端(MAX6675环境温度)与0°C虚拟基准之间的差异。对于K型热电偶,电压变化为41µV /°C,可通过以下线性方程式近似 热电偶特性: VOUT是热电偶输出电压(µV)。TR是远端热电偶结的温度(°C)。TAMB是环境温度(°C)。 2.冷端补偿 热电偶的功能是感应热电偶线两端之间的温度差。 热电偶的热端可以读取0°C至+ 1023.75°C的温度。冷端(安装MAX6675的电路板的环境温度)只能在-20°C至+ 85°C的范围内变化。当冷端温度波动时,MAX6675继续准确地感应另一端的温差。 MAX6675利用冷端补偿来检测并校正环境温度的变化。该设备使用温度感应二极管将环境温度读数转换为电压。为了进行实际的热电偶温度测量,MAX6675测量来自热电偶输出和检测二极管的电压。器件的内部电路将二极管的电压(感测环境温度)和热电偶电压(感测远端温度减去环境温度)传递到ADC中存储的转换函数,以计算热电偶的热端温度。当热电偶冷端和MAX6675处于相同温度时,可实现***佳性能。避免在MAX6675附近放置发热设备或元件,因为这可能会产生与冷端有关的错误。 3.数字化 ADC将冷端二极管的测量值与放大后的热电偶电压相加,并将12位结果读出到SO引脚上,全零序列表示热电偶读数为0°C。全部为1的顺序表示热电偶读数为+ 1023.75°C。 4.串行接口 MAX6675与微控制器接口的典型电路。 通过串行接口传输数据,强制CS为低电平将立即停止任何转换过程,并在SCK处施加时钟信号以读取SO处的结果, 通过强制CS高电平来启动新的转换过程。 完整的串行接口读取需要16个时钟周期,在时钟的下降沿读取16个输出位,第一位D15是伪符号位,始终为0,D14–D3位包含从MSB到LSB的转换温度,当热电偶输入断开时,D2位通常为低电平,而变为高电平, D1为低电平,为MAX6675提供器件ID,D0为三态,图1a是串行接口协议,2是SO输出。 四、程序设计 基于LED Demo新建项目,在项目文件夹appinclude中新增头文件“soft_spi.h”,定义SPI传输涉及的数据变量,如大端MSBFIRST,时钟电平延时SOFTSPI_CLOCK_DIV2(本例以1us为基准,进行延时,得到相应脉冲宽度,其最小值已经超过MAX6675要求的100ns,用于传输速率较低的场合,但是如果用于显示屏,可能会出现刷新慢,卡顿的现象,要求高的话,建议采用硬件SPI),采用传输模式SOFTSPI_MODE2,SPI接口引脚PA7、PA25、PA4,其中MISO(PA7)为输入引脚,其它全为输出引脚。 初始化函数spi_pinmux_init(),工作模式setDataMode(uint8_t),传输函数transfer16(uint16_t data),读取温度值read_temp(uint16_t data)。 #ifndef __SOFTSPI_H_ #define __SOFTSPI_H_ //#include "stdint.h" #include #include #include "drv/gpio_pin.h" #include #ifndef LSBFIRST #define LSBFIRST 0 #endif #ifndef MSBFIRST #define MSBFIRST 1 #endif #define SOFTSPI_MODE0 0x00 #define SOFTSPI_MODE1 0x04 #define SOFTSPI_MODE2 0x08 #define SOFTSPI_MODE3 0x0C #define SOFTSPI_MISO PA7 #define SOFTSPI_MOSI PA25 #define SOFTSPI_SCK PA4 void wait(uint32_t del); void spi_pinmux_init(); void setBitOrder(uint8_t); void setDataMode(uint8_t); uint8_t transfer(uint8_t); uint16_t transfer16(uint16_t data); uint16_t read_temp(uint16_t data); #endif 在项目文件夹appsrc中新增源文件“soft_spi.c” #include "app_config.h" #include "soft_spi.h" #ifdef CONFIG_SOFT_SPI_MODE csi_gpio_pin_t _miso; csi_gpio_pin_t _mosi; csi_gpio_pin_t _sck; uint8_t _CPOL; //CPOL Clock Polarity,时钟极性 uint8_t _CPHA; //CPHA Clock Phase,时钟相位 uint8_t _delay; uint8_t _order; void spi_pinmux_init() { //按照SOFTSOFTSPI_MODE2 进行初始化 csi_pin_set_mux(PA7, PIN_FUNC_GPIO); csi_pin_set_mux(PA25, PIN_FUNC_GPIO); csi_pin_set_mux(PA4, PIN_FUNC_GPIO); csi_gpio_pin_init(&_miso, PA7); csi_gpio_pin_dir(&_miso, GPIO_DIRECtiON_INPUT); csi_gpio_pin_init(&_mosi, PA25); csi_gpio_pin_dir(&_mosi, GPIO_DIRECTION_OUTPUT); csi_gpio_pin_init(&_sck, PA4); csi_gpio_pin_dir(&_sck, GPIO_DIRECTION_OUTPUT); _CPHA = 0; _CPOL = 1; _delay = 2; _order = MSBFIRST; } void setBitOrder(uint8_t order) { _order = order & 1; } void setDataMode(uint8_t mode) { //CPOL(Clock Polarity,时钟极性)表示SCK在空闲时为高电平还是低电平。当CPOL=0,SCK空闲时为低电平,当CPOL=1,SCK空闲时为高电平。 //CPHA(Clock Phase,时钟相位)表示SCK在第几个时钟边缘采样数据。当CPHA=0,在SCK第一个边沿采样数据,当CPHA=1,在SCK第二个边沿采样数据。 switch (mode) { case SOFTSPI_MODE0: _CPOL = 0; //CPOL _CPHA = 0; //CPHA break; case SOFTSPI_MODE1: _CPOL = 0; _CPHA = 1; break; case SOFTSPI_MODE2: _CPOL = 1; _CPHA = 0; break; case SOFTSPI_MODE3: _CPOL = 1; _CPHA = 1; break; } csi_gpio_pin_write(&_sck, _CPOL ? GPIO_PIN_HIGH : GPIO_PIN_LOW); } void wait(uint32_t dl) { for (uint32_t i = 0; i < dl; i++) { udelay(dl); } } uint8_t transfer(uint8_t val) { uint8_t out = 0; if (_order == MSBFIRST) { uint8_t v2 = ((val & 0x01) << 7) | ((val & 0x02) << 5) | ((val & 0x04) << 3) | ((val & 0x08) << 1) | ((val & 0x10) >> 1) | ((val & 0x20) >> 3) | ((val & 0x40) >> 5) | ((val & 0x80) >> 7); val = v2; } uint8_t del = _delay >> 1; uint8_t bval = 0; // // CPOL := 0, CPHA := 0 => INIT = 0, PRE = Z|0, MID = 1, POST = 0 // CPOL := 1, CPHA := 0 => INIT = 1, PRE = Z|1, MID = 0, POST = 1 // CPOL := 0, CPHA := 1 => INIT = 0, PRE = 1 , MID = 0, POST = Z|0 // CPOL := 1, CPHA := 1 => INIT = 1, PRE = 0 , MID = 1, POST = Z|1 // csi_gpio_pin_state_t sck = (_CPOL) ? GPIO_PIN_HIGH : GPIO_PIN_LOW; for (uint8_t bit = 0u; bit < 8u; bit++) { if (_CPHA) { sck ^= GPIO_PIN_HIGH; csi_gpio_pin_write(&_sck, sck); wait(del); } // ... Write bit csi_gpio_pin_write(&_mosi, ((val & (1< sck ^= 1u; csi_gpio_pin_write(&_sck, sck); // ... Read bit { bval = csi_gpio_pin_read(&_miso); if (_order == MSBFIRST) { out <<= 1; out |= bval; } else { out >>= 1; out |= bval << 7; } } wait(del); if (!_CPHA) { sck ^= 1u; csi_gpio_pin_write(&_sck, sck); } } return out; } uint16_t transfer16(uint16_t data) { union { uint16_t val; struct { uint8_t l***; uint8_t m***; }; } in, out; in.val = data; if ( _order == MSBFIRST ) { out.m*** = transfer(in.m***); out.l*** = transfer(in.l***); } else { out.l*** = transfer(in.l***); out.m*** = transfer(in.m***); } return out.val; } uint16_t read_temp(uint16_t data) { uint16_t temp,temp0; temp0 = transfer16(data); temp0 = temp0 >> 3; temp =temp0 *0.25; return temp; } #endif 五、测试程序 在main函数中添加测试代码,开发板上短接J3:3、5引脚, #include "soft_spi.h" spi_pinmux_init(); uint32_t t0; while (1) { t0 = transfer16(0xffff); printf("transfer16 test:%dnr",t0); printf("temprature:%dnr",read_temp(0x7fff)); } 测试效果图: 六、总结 通过本项目,用RVB2601的GPIO实现了SPI时序逻辑接口的数据传输,我感受到了用CDK开发的乐趣,总体上很满意!美中不足的地方,就是板子引出的IO口少了点,为了使用这几个引脚,暂时把LED的连接断开了,后面有时间会进行简单的扩展;另外还有预定义功能,#ifdef 和#endif之间的代码块,如果不参与编译,也没有任何提示,在引用其间函数的时候,会出现undefined reference报错,定位的行号也不准确,给调试造成困难,希望后面的版本能够改进一下! 文章转载自:平头哥芯片开放社区 作者:sqhone |
|
相关推荐
|
|
只有小组成员才能发言,加入小组>>
【平头哥Sipeed LicheeRV 86开发板试用体验】Waft初体验
15655 浏览 1 评论
13703 浏览 4 评论
【平头哥Sipeed LicheeRV 86开发板试用体验】四、烧写waft系统&搭建waft测试环境
19620 浏览 2 评论
59026 浏览 19 评论
【限时福利】加入芯片开发社区,领100G电子工程师资料大礼包
87687 浏览 121 评论
邀请函 | 3月2日 来上海参加平头哥“玄铁RISC-V生态大会”
742浏览 0评论
读书分享会 | 玄铁RISC-V处理器入门与实战电子书免费下载!
631浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 05:54 , Processed in 0.382851 second(s), Total 39, Slave 32 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号