资讯详情

用GPIO模拟SPI接口读取传感器数据

本文以平头哥开发板为基础RVB2601了2601的使用情况GPIO模拟SPI实现时序逻辑SPI协议按照特定温度传感器的时间顺序读取数据和示例程序

一、概述

SPI(SerialPeripheralInterface,串行外设界面)是一种全双工同步串行通信界面MCU与各种外围设备串行通信,最高通信速度为25MHz以上,SPI接口主要用于EEPROM、FLASH、实时时钟,网络控制器,OLED显示驱动器,AD转换器,数字信号处理器、数字信号解码器,温度传感器等设备之间。

如下图,RVB2601板载两个SPI接口,SPI0连接到W传输网络信息,SPI1连接到OLED,理论上用于数据显示,一个SPI多个设备可以挂在总线上,但如果每个设备的时间逻辑和时钟频率不一致,就会导致数据传输效率低、读写错误甚至死机。所以我借鉴Arduino软件模拟在开发板上SPI以平头哥为基础RVB2601开发板,通过GPIO连接外设,实现连接SPI接口的温度传感器,并读取数据。

二、SPI时序逻辑(参考)STM32软件模拟SPI读写Flash存储器)

SPI主设备输出和从设备输入通常由四条线组成(MasterOutputSlaveInput,MOSI),从设备输出(MasterInputSlaveOutput,MISO),一个时钟信号(SerialClock,SCLK),从设备中选择一个(ChipSelect,CS)。与I2C同样,协议相对简单,也可以使用GPIO模拟SPI时序。

SPI一个主机可以连接单个或多个从机,每个从机都使用一个引脚进行片选。物理连接示意图如图21所示.1.1和图21.1.2所示。

数据交换

在SCK在时钟周期的驱动下,MOSI和MISO同时,如图所示21.1.3可视为虚拟环形拓扑结构。

主机和从机都有移位寄存器,主机移位寄存器数据通过MOSI将数据写入从机移位寄存器,此时从机移位寄存器的数据也通过MISO传输给主机,实现两个移位寄存器的数据交换。主机和从机器同时发送和接收,就像一个环。

如果主机只写从机,主机只需要忽略接收到的从机数据。如果主机想读取从机数据,主机需要发送空数据来触发从机数据。

传输模式

SPI有四种传输方式,如表21.1.2主要区别在于CPOL和CPHA的不同。

CPOL(ClockPolarity,时钟极性)表示SCK空闲时间是高电平还是低电平。CPOL=0,SCK空闲时间是低电平,当CPOL=1,SCK空闲时间为高电平。

CPHA(ClockPhase,时钟相位)表示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)。MAX数字控制器还包括冷端补偿检测和校正,SPI兼容接口以及相关的控制逻辑。MAX6675设计用于与外部微控制器或其他智能设备一起使用恒温、过程控制或监控应用。

1.温度转换

MAX6675包括信号调节硬件,可以将热电偶的信号转换为与ADC与输入通道兼容的电压。T 和T-输入连接到内部电路可以减少热电偶导线引入的噪声误差。

在将热电电压转换为等效温度值之前,必须补偿热电偶冷端(MAX6675环境温度°C虚拟基准之间的差异。对于K型热电偶,电压变为41μV/°C,以下线性方程可以近似

热电偶特性:

VOUT是热电偶输出电压(μV)。TR是远端热电偶结的温度(°C)。TAMB是环境温度(°C)。

2.冷端补偿

热电偶的功能是感应热电偶线两端之间的温差。热电偶的热端可读取0°C至 1023.75°C温度。冷端(安装MAX电路板的环境温度仅为-20°C至 85°C范围内的变化。当冷端温度波动时,MAX继续准确感应另一端的温差。

MAX6675利用冷端补偿来检测和校正环境温度的变化。该设备使用温度感应二极管将环境温度读数转换为电压。实际的热电偶温度测量,MAX来自热电偶输出和二极管电压检测的6675。装置内部电路将二极管的电压(环境温度感知)和热电偶电压(环境温度感知远端温度降低)传输到ADC存储在中间的转换函数计算热电偶的热端温度。当热电偶冷端和MAX当6675处于相同温度时,可以实现MAX最佳性能6675。避免在MAX加热设备或部件放置在6675附近,因为这可能会导致与冷端相关的错误。

3.数字化

ADC将冷端二极管的测量值与放大后的热电偶电压相加,读取12位结果SO全零序列表示热电偶读数为0°C。所有1的顺序表示热电偶读数为 1023.75°C。

4.串行接口

MAX6675与微控制器接口的典型电路。

数据通过串行串行接口传输CS任何转换过程任何转换过程都将立即停止SCK施加时钟信号读取SO处的结果,通过强制CS高电平启动新的转换过程。

完整的串行接口读取需要16个时钟周期,16个输出位沿着时钟下降,第一位D15是伪符号位,总是0,D14–D3位包含从MSB到LSB当热电偶输入断开时,转换温度,D两位通常是低电平,变成高电平,D为低电平,为MAX6675提供器件ID,D0为三态,图1a是串行接口协议,2是SO输出。

四、程序设计

基于LEDDemo新项目,项目文件夹app\include新增文件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_tdata),读取温度值read_temp(uint16_tdata)。

#ifndef__SOFTSPI_H_ #define__SOFTSPI_H_  //#include"stdint.h" #include<stdio.h> #include<string.h> #include"drv/gpio_pin.h" #include<drv/pin.h>  #ifndefLSBFIRST #defineLSBFIRST0 #endif #ifndefMSBFIRST #defineMSBFIRST1 #endif  #defineSOFTSPI_MODE00x00 #defineSOFTSPI_MODE10x04 #defineSOFTSPI_MODE20x08 #defineSOFTSPI_MODE30x0C  #defineSOFTSPI_MISOPA7 #defineSOFTSPI_MOSIPA25 #defineSOFTSPI_SCKPA4  voidwait(uint32_tdel); voidspi_pinmux_init(); voidsetBitOrder(uint8_t); voidsetDataMode(uint8_t); uint8_ttransfer(uint8_t); uint16_ttransfer16(uint16_tdata); uint16_tread_temp(uint16_tdata); #endif

在项目文件夹中app\src中新增源文件sot_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<<bit)) ? GPIO_PIN_HIGH : GPIO_PIN_LOW));
        wait(del);
        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 lsb;
			uint8_t msb;
		};
	} in, out;
  	in.val = data;
	if ( _order == MSBFIRST ) {
		out.msb = transfer(in.msb);
		out.lsb = transfer(in.lsb);
	} else {
		out.lsb = transfer(in.lsb);
		out.msb = transfer(in.msb);
	}
	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:%d\n\r",t0);
		printf("temprature:%d\n\r",read_temp(0x7fff));
    }

测试效果图:

标签: 速度传感器导线pin二极管sot

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台