目錄
一、系統(tǒng)接線部分
1.1 硬件清單
1.2 接線方案表
1.3 具體接線圖
1.4 連接實(shí)物圖
二、安裝與使用部分
三、代碼講解部分
3.1 Softwire 正確初始化序列
3.3圓環(huán)儀表盤(pán)繪制與局部刷新策略
3.4音量控制及多頻率告警
3.5 SCD4x庫(kù)API使用
① CRC-8 校驗(yàn)原理
②數(shù)據(jù)就緒輪詢機(jī)制
四、項(xiàng)目結(jié)果演示
4.1操作流程
4.2 視頻演示
五、工作原理講解
5.1 SCD41 通信時(shí)序
5.2 自動(dòng)自校準(zhǔn)機(jī)制
5.3旋轉(zhuǎn)編碼器原理
六、常見(jiàn)問(wèn)題解答(FAQ)
Q1:碼器旋轉(zhuǎn)方向反了,或者非常靈敏轉(zhuǎn)一格跳很多頁(yè)?
Q2:蜂鳴器一直響或完全不響?
Q3:為什么CO?數(shù)據(jù)長(zhǎng)時(shí)間顯示"--"?
項(xiàng)目概述
本項(xiàng)目基于零知派標(biāo)準(zhǔn)板(主控 STM32F103RBT6)驅(qū)動(dòng) Sensirion SCD41 NDIR CO?傳感器,配合 ST7789 240×240 TFT 顯示屏、旋轉(zhuǎn)編碼器和無(wú)源蜂鳴器,實(shí)現(xiàn)了一套完整的室內(nèi)空氣質(zhì)量實(shí)時(shí)監(jiān)測(cè)系統(tǒng)。系統(tǒng)采用淺色簡(jiǎn)約主題,以圓環(huán)儀表盤(pán)的形式同屏展示 CO?濃度、溫度、濕度三路數(shù)據(jù),并通過(guò)旋轉(zhuǎn)編碼器在四個(gè)功能頁(yè)面間自由切換,電位器實(shí)時(shí)調(diào)節(jié)告警音量,SW 按鍵一鍵切換靜音
項(xiàng)目難點(diǎn)
問(wèn)題描述:SoftWire 時(shí)序在多外設(shè)共存場(chǎng)景下崩潰,導(dǎo)致 SCD41 無(wú)法初始化
解決方案:啟動(dòng)畫(huà)面不調(diào)用 beep()、 TFT init 后加 100ms 穩(wěn)定延時(shí)、使用全局 Wire 對(duì)象
一、系統(tǒng)接線部分
1.1 硬件清單
| 序號(hào) | 模塊 | 規(guī)格 | 數(shù)量 |
|---|---|---|---|
| 1 | 零知派標(biāo)準(zhǔn)板 | STM32F103RBT6,Arduino 兼容 | 1 |
| 2 | CO?傳感器 | Sensirion SCD41,I2C,3.3V | 1 |
| 3 | TFT 顯示屏 | ST7789,240×240,SPI | 1 |
| 4 | 旋轉(zhuǎn)編碼器 | EC11,帶按鍵 SW | 1 |
| 5 | 無(wú)源蜂鳴器 | 3.3V,支持 PWM 驅(qū)動(dòng) | 1 |
| 6 | 滑動(dòng)變阻器 | 10kΩ,線性 | 1 |
| 7 | 杜邦線 | 母對(duì)母 | 若干 |
1.2 接線方案表
嚴(yán)格按照代碼中的宏定義進(jìn)行接線,不得隨意更改,否則編碼器中斷和 ADC 會(huì)失效
①SCD41 CO?傳感器
| SCD41 引腳 | 零知派標(biāo)準(zhǔn)板 | 代碼定義 | 說(shuō)明 |
|---|---|---|---|
| SDA | A4 | SoftWire SDA | 軟件 I2C 數(shù)據(jù) |
| SCL | A5 | SoftWire SCL | 軟件 I2C 時(shí)鐘 |
| VDD | 5V | — | 供電,注意噪聲 |
| GND | GND | — | 接地 |
旋轉(zhuǎn)編碼器 EC11
| 編碼器引腳 | 零知派標(biāo)準(zhǔn)板 | 代碼定義 | 說(shuō)明 |
|---|---|---|---|
| CLK(A相) | D6 | #define ENC_CLK 6 | 接外部中斷 |
| DT(B相) | D12 | #define ENC_DT 12 | 接外部中斷 |
| SW(按鍵) | D14 | #define ENC_SW 14 | INPUT_PULLUP |
| VCC | 5V | — | — |
| GND | GND | — | — |
蜂鳴器 & 電位器
| 器件 | 零知派標(biāo)準(zhǔn)板 | 代碼定義 | 說(shuō)明 |
|---|---|---|---|
| 蜂鳴器 + | D3 | #define BUZZER_PIN 3 | 數(shù)字輸出,PWM |
| 蜂鳴器 ? | GND | — | — |
| 電位器中間腳 | A0 | #define VOLUME_PIN A0 | 模擬輸入,12位ADC |
| 電位器兩端 | 3.3V / GND | — | 左側(cè)接3.3V / 右側(cè)接GND |
請(qǐng)注意:ST7789顯示屏直插零知派標(biāo)準(zhǔn)板TFT引腳,無(wú)需單獨(dú)接線
1.3 具體接線圖

編碼器 CLK和 DT接到支持attachInterrupt外部中斷的引腳D6和D12
1.4 連接實(shí)物圖

二、安裝與使用部分
2.1 開(kāi)源平臺(tái)-輸入"SCD41"并搜索-代碼下載自動(dòng)打開(kāi)

2.2 連接-驗(yàn)證-上傳

2.3 調(diào)試-串口監(jiān)視器

三、代碼講解部分
本項(xiàng)目代碼基于SparkFun_SCD4x_Arduino_Library(底層使用SoftWire)和Adafruit_ST7789驅(qū)動(dòng)
3.1 Softwire 正確初始化序列
// setup() 中的傳感器初始化序列 // Step 1:TFT 初始化(SPI外設(shè)配置) tft.init(240, 240); tft.setRotation(3); tft.fillScreen(COL_BG); showSplash(); // ← 純顯示,絕對(duì)不調(diào)用 beep() // Step 2:等待 SPI 外設(shè)穩(wěn)定,給 I2C 上拉恢復(fù)時(shí)間 delay(100); // ← 這 100ms 是解決 err=268 的關(guān)鍵 // Step 3:?jiǎn)?dòng) SoftWire 總線 Wire.begin(); // 使用 SoftWire.cpp 末尾定義的全局 Wire 對(duì)象 delay(50); // I2C 總線電平建立穩(wěn)定時(shí)間 // Step 4:SparkFun 庫(kù)初始化 // begin(wirePort, measBegin, autoCalibrate, skipStop, pollDevType) bool ok = mySensor.begin(Wire, true, true, false, true);
參數(shù)說(shuō)明:
| 參數(shù) | 值 | 含義 |
|---|---|---|
| wirePort | Wire | 使用全局 SoftWire 對(duì)象 |
| measBegin | true | 初始化后立即啟動(dòng)周期測(cè)量 |
| autoCalibrate | true | 啟用 SCD41 自動(dòng)校準(zhǔn)(ASC) |
| skipStop | false | 先執(zhí)行 stopPeriodicMeasurement 確保干凈狀態(tài) |
| pollDevType | true | 讀取 feature set 版本,自動(dòng)識(shí)別 SCD40/41 |
SoftWire 的 NOP-loop 時(shí)序在 STM32 多外設(shè)環(huán)境下不夠穩(wěn)定,導(dǎo)致該命令的 CRC 校驗(yàn)失敗返回 268。SparkFun 的 begin() 通過(guò) getSerialNumber() 的 CRC 校驗(yàn)?zāi)茯?yàn)證通信正常
3.2旋轉(zhuǎn)編碼器四態(tài)查表消抖算法
本項(xiàng)目使用格雷碼查表法,配合累積步數(shù)閾值,實(shí)現(xiàn)準(zhǔn)確的方向判斷
// 16個(gè)狀態(tài)轉(zhuǎn)移,每個(gè)值代表方向變化量(+1/-1/0)
const int8_t encoderTable[] = {
0,-1, 1, 0,
1, 0, 0,-1,
-1, 0, 0, 1,
0, 1,-1, 0
};
void updateEncoder() {
uint8_t clk = digitalRead(ENC_CLK); // A相
uint8_t dt = digitalRead(ENC_DT); // B相
uint8_t encoded = (clk < 1) | dt; // 拼成2位格雷碼
// 用上一狀態(tài)+當(dāng)前狀態(tài)組成4位索引,查表得方向
int8_t dir = encoderTable[(lastEncoded < 2) | encoded];
if (dir != 0) {
accSteps += dir;
// 累積4步才觸發(fā)翻頁(yè),過(guò)濾抖動(dòng)產(chǎn)生的虛假脈沖
if (accSteps >= 4) {
accSteps = 0;
if (millis() - lastEncTrigTime > ENC_COOLDOWN) {
pageCW = true; // 順時(shí)針:下一頁(yè)
lastEncTrigTime = millis();
}
} else if (accSteps <= -4) {
accSteps = 0;
if (millis() - lastEncTrigTime > ENC_COOLDOWN) {
pageCCW = true; // 逆時(shí)針:上一頁(yè)
lastEncTrigTime = millis();
}
}
}
lastEncoded = encoded;
}
// 編碼器通過(guò) attachInterrupt 綁定到兩個(gè)引腳
attachInterrupt(digitalPinToInterrupt(ENC_CLK), updateEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENC_DT), updateEncoder, CHANGE);
// 在 loop() 主循環(huán)中,中斷設(shè)置的標(biāo)志位被消費(fèi)
if (pageCW)
{
pageCW = false;
currentPage = (currentPage+1)%PAGE_COUNT;
lastPage = -2;
}
if (pageCCW)
{
pageCCW = false;
currentPage = (currentPage-1+PAGE_COUNT)%PAGE_COUNT;
lastPage = -2;
}
lastPage=-2 是強(qiáng)制全刷新標(biāo)志,翻頁(yè)時(shí)重繪整個(gè)屏幕避免殘影
3.3圓環(huán)儀表盤(pán)繪制與局部刷新策略
圓環(huán)繪制是本項(xiàng)目視覺(jué)效果的核心,也是性能優(yōu)化的重點(diǎn)
// 基礎(chǔ)弧段繪制:從 startDeg 到 endDeg(以正上方為0°,順時(shí)針)
void drawArcSection(int16_t cx, int16_t cy, int16_t r,
int16_t startDeg, int16_t endDeg, uint16_t color) {
for (int16_t deg = startDeg; deg <= endDeg; deg++) {
// 坐標(biāo)系旋轉(zhuǎn):-90° 使 0° 指向正上方
float rad = (deg - 90) * PI / 180.0f;
tft.drawPixel(cx + (int16_t)(r * cosf(rad)),
cy + (int16_t)(r * sinf(rad)), color);
}
}
// 圓環(huán)(多層弧段疊加形成寬度)
void drawRingArc(int16_t cx, int16_t cy,
int16_t innerR, int16_t outerR,
int16_t startDeg, int16_t endDeg, uint16_t color) {
for (int16_t r = innerR; r <= outerR; r++)
drawArcSection(cx, cy, r, startDeg, endDeg, color);
}
圓環(huán)繪制
// Step1:擦除整個(gè)圓環(huán)區(qū)域(用背景色填充外圓) tft.fillCircle(cx, cy, outerR+2, COL_BG); // Step2:繪制底層灰色軌道(300°滿量程) drawRingArc(cx, cy, innerR, outerR, 0, 300, COL_RING_BG); // Step3:繪制有色填充弧(根據(jù)數(shù)據(jù)值映射到0~300°) int16_t arc = (int16_t)map(constrain(co2, 400, 2500), 400, 2500, 0, 300); if (arc > 0) drawRingArc(cx, cy, innerR, outerR, 0, arc, co2Color(co2)); // Step4:填充圓心區(qū)域(遮住innerR以內(nèi)的像素,恢復(fù)白底) tft.fillCircle(cx, cy, innerR-1, COL_BG);
局部刷新策略
// 只有首次進(jìn)入頁(yè)面或強(qiáng)制刷新時(shí)(full=true)才重繪靜態(tài)背景
void drawPageCO2(bool full) {
if (full) {
tft.fillScreen(COL_BG); // 僅此處全屏清空
drawTopBar("CO2 Detail");
drawBottomBar("TURN to switch");
}
// 以下每次循環(huán)都執(zhí)行:只刷新圓環(huán)和數(shù)值區(qū)域
// 數(shù)值文字前先精確擦除其占用矩形
tft.fillRect(cx-48, labelY-12, 88, 16, COL_BG); // 擦除等級(jí)標(biāo)簽
// 再重繪
tft.print(co2Label(co2));
}
bool full = (currentPage != lastPage);
lastPage = currentPage;
full 為 true 只在翻頁(yè)瞬間,之后每幀都是 false,只刷新有數(shù)據(jù)變化的區(qū)域,大幅減少 SPI 傳輸量
3.4音量控制及多頻率告警
蜂鳴器通過(guò)軟件 PWM(手動(dòng)控制高低電平時(shí)間)實(shí)現(xiàn)不同頻率,由電位器 ADC 值控制占空比從而調(diào)節(jié)響度
void updateVolume() {
int adc = analogRead(VOLUME_PIN); // STM32 12位ADC:0~4095
// 映射到 10%~90% 占空比,防止0%(無(wú)聲)和100%(直流,損壞蜂鳴器)
volumePercent = (uint8_t)map(adc, 0, 4095, 10, 90);
}
void beep(uint16_t freq, uint16_t dur) {
if (freq == 0 || muteEnabled) return;
uint32_t period = 1000000UL / freq; // 周期(微秒)
uint32_t highTime = period * volumePercent / 100; // 高電平時(shí)間
uint32_t lowTime = period - highTime; // 低電平時(shí)間
uint32_t cycles = (uint32_t)dur * 1000UL / period; // 總循環(huán)次數(shù)
for (uint32_t i = 0; i < cycles; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delayMicroseconds(highTime);
digitalWrite(BUZZER_PIN, LOW);
delayMicroseconds(lowTime);
}
}
三檔告警頻率對(duì)應(yīng)表:
| CO? 范圍 | 等級(jí) | 圓環(huán)顏色 | 告警行為 |
|---|---|---|---|
| 0~400 ppm | WAIT | 灰色(數(shù)據(jù)未就緒) | 無(wú) |
| 400~800 ppm | GOOD | 綠色 0x07C0 | 無(wú)告警 |
| 800~1000 ppm | FAIR | 青綠 0x0454 | 單次 440Hz 短鳴 |
| 1000~1500 ppm | POOR | 琥珀黃 0xFEA0 | 雙次 440Hz 鳴叫 |
| 1500~2000 ppm | BAD | 橙色 0xFC40 | 三次 280Hz 告警 |
| >2000 ppm | CRIT | 紅色 0xF800 | 三次 280Hz 急促告警 |
void handleBuzzer() {
if (!dataValid) return;
// 節(jié)流:最快每 4 秒告警一次,避免持續(xù)噪音
if (millis() - lastBeepTime < BEEP_INTERVAL) return;
if (co2 > CO2_BAD) { beepPattern(BEEP_CRIT, 3, 200, 150); lastBeepTime=millis(); }
else if (co2 > CO2_POOR) { beepPattern(BEEP_WARN, 2, 150, 120); lastBeepTime=millis(); }
else if (co2 > CO2_FAIR) { beepPattern(BEEP_WARN, 1, 100, 0); lastBeepTime=millis(); }
}
系統(tǒng)流程圖

3.5 SCD4x庫(kù)API使用
| API | 調(diào)用時(shí)機(jī) | 內(nèi)部原理 |
|---|---|---|
| mySensor.begin(Wire, true, true, false, true) | setup() 一次 | stopPeriodic→CRC序列號(hào)驗(yàn)證→ASC設(shè)置→startPeriodic |
| mySensor.readMeasurement() | loop() 輪詢 | 內(nèi)部先調(diào) getDataReadyStatus(),bit[10:0]≠0才讀;一次讀取 9 字節(jié)含3組 CRC |
| mySensor.getCO2() | 數(shù)據(jù)就緒后 | 返回 uint16_t,單位 ppm,范圍 400~5000 |
| mySensor.getTemperature() | 數(shù)據(jù)就緒后 | 返回 float,公式:-45 + 175×rawT/65535 |
| mySensor.getHumidity() | 數(shù)據(jù)就緒后 | 返回 float,公式:100×rawH/65535 |
① CRC-8 校驗(yàn)原理
//x^8+x^5+x^4+1 = 0x31
uint8_t SCD4x::computeCRC8(uint8_t data[], uint8_t len)
{
uint8_t crc = 0xFF; //Init with 0xFF
for (uint8_t x = 0; x < len; x++)
{
crc ^= data[x]; // XOR-in the next input byte
for (uint8_t i = 0; i < 8; i++)
{
if ((crc & 0x80) != 0)
crc = (uint8_t)((crc < 1) ^ 0x31);
else
crc <= 1;
}
}
return crc; //No output reflection
}
SCD41 每?jī)勺止?jié)數(shù)據(jù)后附一字節(jié) CRC,多項(xiàng)式為 x?+x?+x?+1 = 0x31,初始值 0xFF。SparkFun 庫(kù)在每次 I2C 讀取后自動(dòng)驗(yàn)證,校驗(yàn)失敗則 readMeasurement() 返回 false
②數(shù)據(jù)就緒輪詢機(jī)制
bool SCD4x::readMeasurement(void)
{
//Verify we have data from the sensor
if (getDataReadyStatus() == false)
return (false);
scd4x_unsigned16Bytes_t tempCO2;
tempCO2.unsigned16 = 0;
scd4x_unsigned16Bytes_t tempHumidity;
tempHumidity.unsigned16 = 0;
scd4x_unsigned16Bytes_t tempTemperature;
tempTemperature.unsigned16 = 0;
_i2cPort->beginTransmission(SCD4x_ADDRESS);
_i2cPort->write(SCD4x_COMMAND_READ_MEASUREMENT >> 8); //MSB
_i2cPort->write(SCD4x_COMMAND_READ_MEASUREMENT & 0xFF); //LSB
if (_i2cPort->endTransmission() != 0)
return (false); //Sensor did not ACK
delay(1); //Datasheet specifies this
#if SCD4x_ENABLE_DEBUGLOG
uint8_t receivedBytes = (uint8_t)
#endif // if SCD4x_ENABLE_DEBUGLOG
_i2cPort->requestFrom((uint8_t)SCD4x_ADDRESS, (uint8_t)9);
bool error = false;
if (_i2cPort->available())
{
byte bytesToCrc[2];
for (byte x = 0; x < 9; x++)
{
byte incoming = _i2cPort-?>read();
switch (x)
{
case 0:
case 1:
tempCO2.bytes[x == 0 ? 1 : 0] = incoming; // Store the two CO2 bytes in little-endian format
bytesToCrc[x] = incoming; // Calculate the CRC on the two CO2 bytes in the order they arrive
break;
case 3:
case 4:
tempTemperature.bytes[x == 3 ? 1 : 0] = incoming; // Store the two T bytes in little-endian format
bytesToCrc[x % 3] = incoming; // Calculate the CRC on the two T bytes in the order they arrive
break;
case 6:
case 7:
tempHumidity.bytes[x == 6 ? 1 : 0] = incoming; // Store the two RH bytes in little-endian format
bytesToCrc[x % 3] = incoming; // Calculate the CRC on the two RH bytes in the order they arrive
break;
default: // x == 2, 5, 8
//Validate CRC
uint8_t foundCrc = computeCRC8(bytesToCrc, 2); // Calculate what the CRC should be for these two bytes
if (foundCrc != incoming) // Does this match the CRC byte from the sensor?
{
#if SCD4x_ENABLE_DEBUGLOG
if (_printDebug == true)
{
_debugPort->print(F("SCD4x::readMeasurement: found CRC in byte "));
_debugPort->print(x);
_debugPort->print(F(", expected 0x"));
_debugPort->print(foundCrc, HEX);
_debugPort->print(F(", got 0x"));
_debugPort->println(incoming, HEX);
}
#endif // if SCD4x_ENABLE_DEBUGLOG
error = true;
}
break;
}
}
}
else
{
#if SCD4x_ENABLE_DEBUGLOG
if (_printDebug == true)
{
_debugPort->print(F("SCD4x::readMeasurement: no SCD4x data found from I2C, I2C claims we should receive "));
_debugPort->print(receivedBytes);
_debugPort->println(F(" bytes"));
}
#endif // if SCD4x_ENABLE_DEBUGLOG
return (false);
}
if (error)
{
#if SCD4x_ENABLE_DEBUGLOG
if (_printDebug == true)
_debugPort->println(F("SCD4x::readMeasurement: encountered error reading SCD4x data."));
#endif // if SCD4x_ENABLE_DEBUGLOG
return (false);
}
//Now copy the int16s into their associated floats
co2 = (float)tempCO2.unsigned16;
temperature = -45 + (((float)tempTemperature.unsigned16) * 175 / 65536);
humidity = ((float)tempHumidity.unsigned16) * 100 / 65536;
//Mark our global variables as fresh
co2HasBeenReported = false;
humidityHasBeenReported = false;
temperatureHasBeenReported = false;
return (true); //Success! New data available in globals.
}
寄存器 bit[10:0] 全為 0 表示數(shù)據(jù)未就緒,任意一位為 1 表示可讀
四、項(xiàng)目結(jié)果演示
4.1操作流程
初始化上電

TFT 顯示啟動(dòng)畫(huà)面(CO? MONITOR + Lingzhi Lab 2026),約 1 秒后發(fā)出兩聲短鳴提示初始化成功
顯示 "SCD41 Running..." 動(dòng)態(tài)圓點(diǎn),約 5 秒后第一幀數(shù)據(jù)到達(dá),主界面自動(dòng)切換為實(shí)時(shí)數(shù)據(jù)顯示
主界面(Page 0)
上方 CO? 大圓環(huán) + 下方溫度/濕度小圓環(huán)同時(shí)展示,圓環(huán)顏色隨數(shù)值實(shí)時(shí)變化,右側(cè)等級(jí)標(biāo)簽跟隨更新

旋轉(zhuǎn)編碼器右轉(zhuǎn):翻到下一頁(yè)(CO?詳情 → 溫濕度詳情 → 系統(tǒng)信息 → 循環(huán)回主界面)
旋轉(zhuǎn)編碼器左轉(zhuǎn):翻到上一頁(yè)
SW 按鍵按下
底欄 LIVE 切換為 MUTE(橙色),蜂鳴器靜音;再次按下恢復(fù) LIVE(綠色)

旋轉(zhuǎn)電位器
可以調(diào)節(jié)蜂鳴器告警音量(最小 10% 占空比細(xì)聲,最大 90% 占空比響亮)

CO?溶度超過(guò) 1000ppm蜂鳴器單次告警;超過(guò) 1500ppm 雙次;超過(guò) 2000ppm 三次急促告警,圓環(huán)變紅
4.2 視頻演示
https://live.csdn.net/v/528726?spm=1001.2014.3001.5501
本視頻展示基于零知派標(biāo)準(zhǔn)板驅(qū)動(dòng) Sensirion SCD41 CO?傳感器的完整室內(nèi)空氣質(zhì)量監(jiān)測(cè)系統(tǒng)。演示內(nèi)容包括:系統(tǒng)上電初始化流程、ST7789 TFT 淺色主題三環(huán)儀表盤(pán)實(shí)時(shí)刷新效果、旋轉(zhuǎn)編碼器左右翻頁(yè)切換四個(gè)顯示界面(主界面/CO?詳情/溫濕度露點(diǎn)/系統(tǒng)信息)、電位器實(shí)時(shí)調(diào)節(jié)蜂鳴器音量、SW 按鍵靜音切換、以及模擬高CO?環(huán)境時(shí)的分級(jí)告警蜂鳴效果
五、工作原理講解
SCD41 采用光聲光譜(PAS)技術(shù),是一種基于 NDIR 的 CO?測(cè)量方案。其工作核心是CO?分子對(duì)波長(zhǎng)約 4.26 μm 的紅外光有強(qiáng)烈的選擇性吸收

傳感器內(nèi)部:
紅外光源周期性發(fā)射寬譜紅外光
光穿過(guò)含有待測(cè)氣體的腔體
特定波長(zhǎng)的光被 CO?分子吸收,剩余光被探測(cè)器接收
通過(guò) Beer-Lambert 定律計(jì)算 CO?濃度
A = ε × c × L
A: 吸光度 ε: 摩爾消光系數(shù)(CO?固有屬性) c: 濃度(即我們要測(cè)的值) L: 光程長(zhǎng)度(腔體固定)
5.1 SCD41 通信時(shí)序
SCD41 的 I2C 地址固定為 0x62,不可更改。通信采用標(biāo)準(zhǔn) I2C 協(xié)議,命令格式為 16 位命令字(MSB 先發(fā))
①寫(xiě)命令時(shí)序

②基礎(chǔ)命令

start_periodic_measurement(0x21B1)發(fā)出后必須等待 5 秒才能讀取,發(fā)送其他命令(如 stop_periodic_measurement 0x3F86)后需等待 500ms 才能繼續(xù)操作
③讀數(shù)據(jù)時(shí)序

④一次完整測(cè)量讀取的數(shù)據(jù)幀格式
總計(jì) 9 字節(jié),每?jī)勺止?jié)數(shù)據(jù)附一字節(jié) CRC-8

溫度換算

濕度換算

5.2 自動(dòng)自校準(zhǔn)機(jī)制
SCD41 內(nèi)置 ASC 算法,假設(shè)傳感器在使用期間至少每周會(huì)暴露一次室外新鮮空氣(約 400ppm)。ASC 會(huì)統(tǒng)計(jì)歷史測(cè)量中的最低 CO?值,并以此為基準(zhǔn)自動(dòng)漂移校準(zhǔn)

5.3旋轉(zhuǎn)編碼器原理
EC11 是一種機(jī)械式增量旋轉(zhuǎn)編碼器,內(nèi)部有兩組相位差 90° 的觸點(diǎn)(A相/CLK 和 B相/DT)。旋轉(zhuǎn)時(shí)兩相產(chǎn)生交替的高低電平變化,通過(guò)判斷兩相的相位超前/滯后關(guān)系確定旋轉(zhuǎn)方向
①光電編碼原理

編碼器內(nèi)部有一個(gè)帶有柵格的光碼盤(pán),紅外發(fā)射管和接收管分別位于碼盤(pán)兩側(cè)、旋轉(zhuǎn)時(shí),柵格交替遮擋光線,產(chǎn)生脈沖信號(hào)
②A/B相信號(hào)產(chǎn)生
正交編碼:兩個(gè)光電傳感器安裝位置相差1/4個(gè)柵格間距,產(chǎn)生相位差90°的A相和B相信號(hào)

正轉(zhuǎn):A 相脈沖的上升沿 / 下降沿超前B 相 90°, A 相先變化,B 相后變化
反轉(zhuǎn):B 相脈沖的上升沿 / 下降沿超前A 相 90°, B 相先變化,A 相后變化
③格雷碼編碼
將 A、B 兩相的當(dāng)前值拼成 2 位二進(jìn)制(encoded = CLK<<1 | DT),與上一次狀態(tài)一起組成 4 位索引((last<<2)|current),查 16 格表直接得到方向值 {+1/-1/0}
順時(shí)針旋轉(zhuǎn)時(shí),A 引腳先于 B 引腳接地,格雷碼按00→01→11→10→00的順序循環(huán)

逆時(shí)針旋轉(zhuǎn)時(shí),B 引腳先于 A 引腳接地,格雷碼按00→10→11→01→00的順序循環(huán)

每旋轉(zhuǎn)一格產(chǎn)生 4 個(gè)有效邊沿,累積滿 ±4 才確認(rèn)一次有效旋轉(zhuǎn)
六、常見(jiàn)問(wèn)題解答(FAQ)
Q1:碼器旋轉(zhuǎn)方向反了,或者非常靈敏轉(zhuǎn)一格跳很多頁(yè)?
A:方向反:交換 CLK 和 DT 的接線,或?qū)?+1/-1 查表結(jié)果對(duì)換。跳頁(yè)過(guò)多:檢查 accSteps 閾值是否為 4,以及 ENC_COOLDOWN 是否設(shè)為 400ms
Q2:蜂鳴器一直響或完全不響?
A:確認(rèn)使用的是無(wú)源蜂鳴器,有源蜂鳴器只能響單一固定頻率,無(wú)法響應(yīng) PWM。完全不響檢查 muteEnabled 是否為 true(底欄顯示 MUTE),以及 D3 引腳接線
Q3:為什么CO?數(shù)據(jù)長(zhǎng)時(shí)間顯示"--"?
A:SCD41 startPeriodicMeasurement 后首幀數(shù)據(jù)需要 5 秒。若超過(guò) 15 秒仍無(wú)數(shù)據(jù),檢查 A4/A5 接線和 供電??膳R時(shí)開(kāi)啟 SparkFun 庫(kù)的 Debug 輸出:mySensor.enableDebugging(Serial)
項(xiàng)目資源整合
SCD41 數(shù)據(jù)手冊(cè): SCD4x Data Sheet
SCD4x庫(kù)文件: sparkfun/SparkFun_SCD4x_Arduino_Library
審核編輯 黃宇
-
STM32
+關(guān)注
關(guān)注
2315文章
11223瀏覽量
375708 -
監(jiān)測(cè)系統(tǒng)
+關(guān)注
關(guān)注
8文章
3124瀏覽量
84800 -
蜂鳴器
+關(guān)注
關(guān)注
12文章
901瀏覽量
47959 -
旋轉(zhuǎn)編碼器
+關(guān)注
關(guān)注
5文章
164瀏覽量
27621
發(fā)布評(píng)論請(qǐng)先 登錄
零知派——STM32+SCD41+旋轉(zhuǎn)編碼器:室內(nèi)CO?智能監(jiān)測(cè)系統(tǒng),三環(huán)可視化儀表盤(pán) + 分級(jí)蜂鳴告警
零知派——STM32+BMP180結(jié)合旋轉(zhuǎn)編碼器多頁(yè)儀表盤(pán)+ESP-01無(wú)線上位機(jī),三級(jí)蜂鳴告警全鏈路IoT氣象監(jiān)控系統(tǒng)
虛擬儀表盤(pán)是未來(lái)的趨勢(shì)嗎?
汽車(chē)儀表盤(pán)電源設(shè)計(jì)
【課程筆記】儀表盤(pán)和數(shù)字圖的應(yīng)用
一般圖表做不了的分析,BI數(shù)據(jù)可視化圖表可以
一文淺析汽車(chē)儀表盤(pán)
集成TPMS功能的儀表盤(pán)設(shè)計(jì)方案解析
集成TPMS功能的電動(dòng)汽車(chē)儀表盤(pán)設(shè)計(jì)解析
汽車(chē)儀表盤(pán)維修_汽車(chē)儀表盤(pán)可以修理嗎_汽車(chē)儀表盤(pán)維修多少錢(qián)
儀表盤(pán)液晶化發(fā)展趨勢(shì)_奧迪在打什么算盤(pán)?
手把手教你完成數(shù)據(jù)儀表盤(pán)的設(shè)計(jì)步驟
磁環(huán)編碼器:精準(zhǔn)定位與高效旋轉(zhuǎn)控制的創(chuàng)新解決方案
先積電源產(chǎn)品在電動(dòng)車(chē)儀表盤(pán)領(lǐng)域的應(yīng)用
零知派——STM32+SCD41+旋轉(zhuǎn)編碼器:室內(nèi)CO?智能監(jiān)測(cè)系統(tǒng),三環(huán)可視化儀表盤(pán) + 分級(jí)蜂鳴告警
評(píng)論