esp8266 spi设备共用CS

1. 非门电路

这个很简单,不细说了哈

2. MAX31865

​ "MAX31865是易于使用的电阻数字转换器,针对铂电阻温度检测器(RTD)进行了优化。外部电阻设置所用RTD的灵敏度,而精密Δ-ΣADC则可转换RTD电阻比。 MAX31865的输入具有高达Q45V的过压故障保护,并具有可编程检测RTD和电缆开路和短路情况的功能"

——来自datasheet
项目需要使用了两个Adafuit设计的MAX31865,同时Adafruit还提供了[Arduino library](https://github.com/adafruit/Adafruit_MAX31865) ,我们小改即可

Adafuit版本的原理图:

3. 一个CS引脚控制两个MAX31865片选

理想电路

使用Fritzing绘制

PS:灵魂走线(小声bb)

原理图

也是用Fritzing整的

细心的读者会发现,该电路少了开篇非门电路的上拉电阻,因为Adafuit版本的原理图已经将CS上拉10k,所以这里可以省略😳

PS:感觉没有Eagle顺手(小声bb)

实测电路

IMG_4563

为了更显情调,在D2引脚上加了一个💡串联一个510Ω接地,这样会随着D2(CS信号)变化Blink

为啥还有一个arduino?

arduino,引出的三条线:

color 使用GPIO 功能
红线 A0 测右边模块CS电平,对应下文Demo的MAX_02
绿线 A1 测左边模块CS电平,对应下文Demo的MAX_01
蓝线 A2 测D2(CS信号)变化电平,其实和绿线电平变化相同

arduino当示波器用,虽然只有3k采样频率,但是总比没有好😅

该示波器基于processing,使用Uno的A0-A3引脚,支持4通道,项目地址

4. 修改库

修改的行都上了注释,若太多原库代码的用"......"表示

Adafruit_MAX31865.h

1
2
3
4
5
6
typedef enum max31865_numwires { 
MAX31865_2WIRE = 0,
MAX31865_3WIRE = 1,
MAX31865_4WIRE = 0,
MAX31865_Double_one_cs_pin = 2 // 添加此枚举常量便于修改Adafruit_MAX31865::begin()
} max31865_numwires_t;
1
2
3
4
5
6
7
8
9
class Adafruit_MAX31865 {
public:
......
static unsigned int trans; // 用于cs计数
static bool change_cs; // 是否改变片选

private:
......
};

Adafruit_MAX31865.cpp

1
2
3
/* 以下代码在库文件包含后和成员函数定义前加入*/
unsigned int Adafruit_MAX31865::trans = 0; // 非负整形计数溢出时归零
bool Adafruit_MAX31865::change_cs = false; // 向Adafruit_MAX31865::begin()传入前三种枚举常量时不修改片选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
boolean Adafruit_MAX31865::begin(max31865_numwires_t wires) {
pinMode(_cs, OUTPUT);
if (wires == MAX31865_Double_one_cs_pin)
{
wires = MAX31865_4WIRE;
++trans; // 首次为奇数

// trans为奇数时,片选MAX_01,偶数时片选MAX_02
if (trans % 2 == 0)
{
change_cs = true;
Serial.print(" MAX_02: ");
digitalWrite(_cs, LOW);
}
else
{
change_cs = false;
Serial.print("MAX_01: ");
digitalWrite(_cs, HIGH); // 第一次必须上拉以执行SPI.begin();
}
}
else
digitalWrite(_cs, HIGH);
......
}

最后将Adafruit_MAX31865::readRegisterN()Adafruit_MAX31865::writeRegister8()

1
digitalWrite(_cs, LOW);
1
digitalWrite(_cs, HIGH);

分别替换为:

1
2
3
4
if(change_cs == false)  
digitalWrite(_cs, LOW);
else
digitalWrite(_cs, HIGH);
1
2
3
4
if(change_cs == false)
digitalWrite(_cs, HIGH);
else
digitalWrite(_cs, LOW);

5. arduino IDE程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* 2020-02-25 OldGerman*/
#include <Adafruit_MAX31865.h>

#define RREF 430.0 /* Max31865参考电阻Rref值: pt100 430Ω, pt1000 4300Ω */
#define RNOMINAL 100.0 /* 零度时pt100电阻值校准, pt1000 为 1000.0 */

/* Node MCU 1.0 pins connected to: */
#define MAX_CS 4 /* D2 */
#define MISO 13 /* D7 */
#define MOSI 12 /* D6 */
#define SCLK 14 /* D5 */

/* 库的SPI总线速率为8MHz时 */
int chAverage = 1; // 示例默认测温采样一次,当采样次数为20时,2秒可读完20次求出平均值
// 单次读取时间100ms,远高于MAX31865寄存器最长读数更新时间21ms

float maxAverageTemp(Adafruit_MAX31865); /* 测温滤波 */

Adafruit_MAX31865 maxESP = Adafruit_MAX31865(MAX_CS, MISO, MOSI, SCLK);

//-------------------main function------------------
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Double MAX31865 4 wire PT100 Sensor Test!");

}

void loop() {
static unsigned int tabline = false;
++tabline;
maxESP.begin(MAX31865_Double_one_cs_pin); //【单线双max模式,四线pt100】
Serial.print(maxAverageTemp(maxESP));
if(tabline %2 == 0)
Serial.println();
}

//`测温滤波 - 多次采样计算平均数
float maxAverageTemp(Adafruit_MAX31865 _maxESP){
float averageTemp = 0;
for(int i = 0; i < chAverage; ++i)
{
averageTemp += _maxESP.temperature(RNOMINAL, RREF);
}

return averageTemp /= chAverage;
}

6. Demo

右侧为arduino示波器,红绿蓝三种电平变化对应上文提及的功能😃

7. 关于修改过的库

度云链接 提取码:p9kl

8MHz总线速度测试正常

没有降低该库的spi速率,全文在提高到8MHz测试的

Adafruit_MAX31865.h

1
static SPISettings max31865_spisettings = SPISettings(8000000/*<--Dual_MAX_one_CS_pin_8000,000测试没问题/*之前:500000*/, MSBFIRST, SPI_MODE1);	//	https://www.arduino.cc/en/Reference/SPISettings

优化Adafruit_MAX31865::readRTD(),避免看门狗复位

另外,我还修改了该库,可在Adafruit_MAX31865.h中选择禁用Adafruit_MAX31865::readRTD()所有的delay(),用milis()和yield()代替,关键代码如下:

Adafruit_MAX31865.h

1
2
3
4
5
6
//  add by oldgerman 20200115
// 使用milis()配合yield()代替readRTD()函数内部的delay()
#define NODELAY // 若注释掉,使用delay()
#ifdef NODELAY
static unsigned long previousMillis = 0; // will store last time rtd was updated
#endif

Adafruit_MAX31865.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
uint16_t Adafruit_MAX31865::readRTD (void) {
clearFault();
enableBias(true);
static uint16_t rtd;
#ifndef NODELAY
delay(10);
uint8_t t = readRegister8(MAX31856_CONFIG_REG);
t |= MAX31856_CONFIG_1SHOT;
writeRegister8(MAX31856_CONFIG_REG, t);

delay(65);
rtd = readRegister16(MAX31856_RTDMSB_REG);

// remove fault
rtd >>= 1;
return rtd;

#elif defined NODELAY
while(true)
{
unsigned long currentMillis_1 = millis();
//Serial.println("currentMillis_1 = "); Serial.print(currentMillis_1); Serial.println();
if(currentMillis_1 - previousMillis >= 10)
{
previousMillis = currentMillis_1;
uint8_t t = readRegister8(MAX31856_CONFIG_REG);
t |= MAX31856_CONFIG_1SHOT;
writeRegister8(MAX31856_CONFIG_REG, t);
while(true)
{
unsigned long currentMillis_2 = millis();
//Serial.println("currentMillis_2 = "); Serial.print(currentMillis_2); Serial.println();
if (currentMillis_2 - previousMillis >= 65)
{
previousMillis = currentMillis_2;
rtd = readRegister16(MAX31856_RTDMSB_REG);
// remove fault
rtd >>= 1;
return rtd;
}
yield(); // 第一次
}
}
yield(); // 第二次
}
#endif
}

yield()简单介绍 - - 来自Sparkfun

由ESP8266 Arduino库的创造者实现,该函数调用后台函数以允许他们执行其任务。

例如,如果您的草图正在等待某人按下连接到引脚12的按钮,则这样的循环将使ESP8266不会崩溃:

1
2
3
4
pinMode(12, INPUT_PULLUP); // Set pin 12 as an input w/ pull-up
while (digitalRead(12) == HIGH) // While pin 12 is HIGH (not activated)
yield(); // Do (almost) nothing -- yield to allow ESP8266 background functions
Serial.println("Button is pressed!"); // Print button pressed message.

实测,禁用delay()后,我在上文中的maxAverageTemp()采样可以提高到20次以上,而不会使看门狗复位,(原先5次以上就很容易复位)😳

最后来个单CS读两个MAX31865每一次采样20次的波形😄

8.后记

74HC138 复用CS

如法炮制,可以用cmos器件74HC138实现3个CS引脚片选8个spi设备

以下来自百度百科:

74HC138译码器可接受3位二进制加权地址输入(A0, A1和A2)

当使能时,提供8个互斥的低有效输出(Y0至Y7),简直太合适了有木有😂