显示屏与桌面作品 / 显示与总线实践发布 内容更新

ESP32 接 ST7789 彩屏:先画色块,再核对方向与偏移

固定PID3787的240×240窗口,逐项核对RGB565、四种旋转与gap;再接SHT40显示真实样本,读取失败立即隐藏旧值。附完整IDF工程和四方向记录。

读完能做什么
获得可切四种方向色卡与SHT40实时页的工程,知道颜色、窗口、传输完成和样本有效分别怎么验。
开始之前
已完成前置教程,能核对GPIO、串口、模块供电并复建固定框架示例。
所需器材
官方 ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,8MB Flash、Octal PSRAM;USB-to-UART 桥接串口115200。、Adafruit PID3787(2022年11月EYESPI修订),SD插槽空;排针已可靠焊接,散排针先由具备条件者焊好并检查、Adafruit PID4885 SHT40,0x44、两只10kΩ片选上拉;短线;可选SDA信号SPST
版本组合
参考板:官方 ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,8MB Flash、Octal PSRAM;USB-to-UART 桥接串口115200。工具/框架:ESP-IDF v5.5.5 / esp_lcd ST7789 + native I2C master
适用范围

官方 ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,8MB Flash、Octal PSRAM;USB-to-UART 桥接串口115200。240×240 RGB565 LITTLE/RGB,SPI4MHz;I2C0x44/100kHz;未实板。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/displays-expanded.md#d03版本:ESP-IDF v5.5.5 / esp_lcd ST7789 + native I2C master验证日期:
构建 / 配置已通过
docs/esp32-evidence/d03-build.log对象:esp32s3 / N8R8版本:ESP-IDF v5.5.5 / esp_lcd ST7789 + native I2C master验证日期:
仿真已通过
docs/esp32-evidence/displays-host-checks.json#d03对象:同源纯逻辑host fixture;不模拟电气/控制器验证日期:
本站实板待验证
未取得实板;观测记录保持空白,未验证光学、总线或供电。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 显示与总线实践

ST7789是控制器名,第一屏还要锁定面板

本篇用Adafruit PID3787的1.54英寸240×240屏,不是135×240、240×320,也不是别的同芯片模块。控制器能寻址240×320 RAM,具体面板只露出其中240×240;所以代码能发数据并不代表窗口位置正确。

先画独立RGB色条、四角编号和1像素边框,再接真实SHT40。这样颜色/方向错不会被复杂界面掩盖,传感器失联也不会继续显示上一条温湿度。四方向照片和真实断线记录仍待实板。

参考:[1] PID3787产品与2022年11月修订[3] Adafruit ST7789固定1.11.0窗口和方向[4] IDF5.5.5 ST7789:RAMCTRL/色序/镜像/偏移

02 / 显示与总线实践

把显示与采样分成两组接线核对

本例走排针接线,使用排针已可靠焊接的同修订PID3787。随附散排针不等于已焊好;未焊时先由具备工具和焊接条件的人完成并检查,不能将排针松插焊孔当作电气连接。EYESPI仍留空,不在本例切换接口。

显示供电用3V3→VIN,模块3V输出留空,GND共地。SCK12、SI11、SO10、TC5、CC13、DC4、RT6、BL7,两个CS分别10kΩ上拉。SD插槽空,TE与EYESPI不用;BL高开低关,背光发亮不等于SPI成功。

SHT40锁PID4885,VIN3V3、GND、SDA8/J1-12、SCL9/J1-15,0x44/100kHz;该模块已有外部上拉,程序内部上拉只是辅助。先断电接好,再经USB-to-UART桥接接口构建/查看115200串口,GPIO43/44留给桥接。

做到这里,你应该看到
先按F07核对PID与模块排针,绝不把稳压输出3V当作VIN。

参考:[1] PID3787产品与2022年11月修订[2] Adafruit具体排针、电源、背光与SO用途[6] DevKitC-1 v1.1排针[7] Adafruit PID4885 SHT40模块[10] IDF5.5.5 S3新I2C主机API

03 / 显示与总线实践

红蓝相反、像素杂色、边缘裁切分别查什么

RGB565把红绿蓝分别放在16位的5/6/5位域:红0xF800、绿0x07E0、蓝0x001F。RGB/BGR决定红蓝位域如何解释;两个字节的传送顺序又是另一个设置。IDF5.5.5的ST7789驱动确实实现data_endian,设置LITTLE后在RAMCTRL置位,匹配ESP32的uint16_t内存布局。

本例RGB元素顺序为RGB、inversion=true,先按模块参考驱动保持这些条件。若纯红纯蓝对调,先查RGB/BGR;若出现杂色再查16位字节序。不要把invert_color当成红蓝互换开关。

参考:[3] Adafruit ST7789固定1.11.0窗口和方向[4] IDF5.5.5 ST7789:RAMCTRL/色序/镜像/偏移

04 / 显示与总线实践

四种方向逐项对应,不靠屏幕正方形猜

Adafruit ST7789 1.11.0在240×240分支设置rowstart=80、rowstart2=0,然后按方向选择MADCTL和起始窗口。本工程用IDF的swap_xy、mirror和set_gap表达同一组设置,表中值也由同源host fixture校验。

按0/1/2/3时,内容都应保留完整边框和四角,且相对于模块排针转向可记录。数值映射通过不证明面板安装方向;实际照片仍需用同一物理参考方位拍摄。

按键swap_xymirror_x / mirror_ygap_x / gap_y
0falsetrue / true0 / 80
1truefalse / true80 / 0
2falsefalse / false0 / 0
3truetrue / false0 / 0

参考:[3] Adafruit ST7789固定1.11.0窗口和方向[4] IDF5.5.5 ST7789:RAMCTRL/色序/镜像/偏移

05 / 显示与总线实践

每轮采样先过两组CRC,失败就撤销valid

SHT40发送高精度命令0xFD,等待10ms后读6字节。前两字节温度、一个CRC,再两字节湿度、一个CRC;CRC初值0xFF、多项式0x31。公式为T=-45+175×raw/65535、RH=-6+125×raw/65535,湿度结果限制在0–100%。这些参数与Sensirion固定1.1.2/0.7.3源一致,本工程用原创小型解码器,不引入Arduino库。

每2秒一轮,I2C发送/接收各有100ms超时。NACK、超时或任何一组CRC坏就将当前valid设false;屏幕显示NO SAMPLE/CHECK I2C,旧数值隐藏。闭合SDA开关后下一次成功采样才恢复数值,没有固定演示温度补空。

参考:[8] Sensirion SHT4x1.1.2:0xFD、10ms与换算[9] Sensirion Core0.7.3:CRC31/FF[10] IDF5.5.5 S3新I2C主机API

06 / 显示与总线实践

完整工程:采样、渲染和DMA各有边界

在IDF5.5.5环境,使用独立构建目录运行下方命令,flash前替换真实桥接端口。下载同时含主CMake、main组件定义、sdkconfig.defaults、versions.lock、host fixture和空观测表。

显示使用7680字节内部DMA条带,等完成回调后复用;串口按键在每轮处理,单次I2C或画面发送仍占用一段时间,本例不承诺硬实时输入。渲染超时停止后续作业且保留缓冲,不把提交返回视作传输完成。

代码 / 配置
idf.py -B /tmp/d03-build -D SDKCONFIG=/tmp/d03-build/sdkconfig -D IDF_TARGET=esp32s3 build
idf.py -B /tmp/d03-build -p /dev/cu.YOUR_UART_PORT flash monitor

参考:[4] IDF5.5.5 ST7789:RAMCTRL/色序/镜像/偏移[5] IDF5.5.5 SPI LCD:DMA事务与完成回调[10] IDF5.5.5 S3新I2C主机API

07 / 显示与总线实践

完整入口、传感器解码和显示组件

文件标签与下载相对路径一致。四种窗口配置、像素测试卡、样本valid与DMA等待都在以下源码里;硬件配置的变更应同时更新锁文件和观测记录。

代码 / 配置
// ---- main/Sht40Logic.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
inline uint8_t sht_crc(const uint8_t* b){uint8_t c=0xff;for(int k=0;k<2;++k){c^=b[k];for(int j=0;j<8;++j)c=(c&0x80)?uint8_t((c<<1)^0x31):uint8_t(c<<1);}return c;}
inline bool decode_sht40(const uint8_t b[6],int& t,int& rh){
 if(sht_crc(b)!=b[2]||sht_crc(b+3)!=b[5])return false;
 uint16_t rt=(uint16_t(b[0])<<8)|b[1],rr=(uint16_t(b[3])<<8)|b[4];
 t=-45000+int((175000LL*rt)/65535);rh=-6000+int((125000LL*rr)/65535);
 if(rh<0)rh=0;
 if(rh>100000)rh=100000;
 return true;
}
// ---- main/Console.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <unistd.h>
#include <fcntl.h>
#include "driver/uart.h"
#include "esp_vfs_dev.h"
inline void init_console(){
 ESP_ERROR_CHECK(uart_driver_install(UART_NUM_0,256,0,0,nullptr,0));
 esp_vfs_dev_uart_use_driver(UART_NUM_0);
 int flags=fcntl(STDIN_FILENO,F_GETFL);fcntl(STDIN_FILENO,F_SETFL,flags|O_NONBLOCK);
}
inline char console_key(){char c=0;return read(STDIN_FILENO,&c,1)==1?c:0;}
// ---- components/hxdisplay/Graphics.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string.h>
namespace hx {
struct View { enum Page { TEST, SENSOR, PROGRESS } page=TEST; unsigned rotation=0,seq=0; bool valid=false; int temperature_mC=0,humidity_mPermille=0; };
// Original 5x7 uppercase/digit bitmap font, one row per byte (5 significant bits).
inline const uint8_t* glyph(char c) {
 struct Entry { char c; uint8_t row[7]; };
 static const Entry f[]={
 {'0',{14,17,19,21,25,17,14}},{'1',{4,12,4,4,4,4,14}},{'2',{14,17,1,2,4,8,31}},
 {'3',{30,1,1,14,1,1,30}},{'4',{2,6,10,18,31,2,2}},{'5',{31,16,16,30,1,1,30}},
 {'6',{14,16,16,30,17,17,14}},{'7',{31,1,2,4,8,8,8}},{'8',{14,17,17,14,17,17,14}},{'9',{14,17,17,15,1,1,14}},
 {'A',{14,17,17,31,17,17,17}},{'B',{30,17,17,30,17,17,30}},{'C',{14,17,16,16,16,17,14}},
 {'D',{30,17,17,17,17,17,30}},{'E',{31,16,16,30,16,16,31}},{'F',{31,16,16,30,16,16,16}},
 {'G',{14,17,16,23,17,17,14}},{'H',{17,17,17,31,17,17,17}},{'I',{14,4,4,4,4,4,14}},
 {'J',{7,2,2,2,2,18,12}},{'K',{17,18,20,24,20,18,17}},{'L',{16,16,16,16,16,16,31}},
 {'M',{17,27,21,21,17,17,17}},{'N',{17,25,21,19,17,17,17}},{'O',{14,17,17,17,17,17,14}},
 {'P',{30,17,17,30,16,16,16}},{'Q',{14,17,17,17,21,18,13}},{'R',{30,17,17,30,20,18,17}},
 {'S',{15,16,16,14,1,1,30}},{'T',{31,4,4,4,4,4,4}},{'U',{17,17,17,17,17,17,14}},
 {'V',{17,17,17,17,17,10,4}},{'W',{17,17,17,21,21,27,17}},{'X',{17,17,10,4,10,17,17}},
 {'Y',{17,17,10,4,4,4,4}},{'Z',{31,1,2,4,8,16,31}},{'.',{0,0,0,0,0,6,6}},
 {'-',{0,0,0,31,0,0,0}},{'+',{0,4,4,31,4,4,0}},{'/',{1,1,2,4,8,16,16}},
 {'%',{25,25,2,4,8,19,19}},{',',{0,0,0,0,6,4,8}},{' ',{0,0,0,0,0,0,0}}
 };
 for(const auto& e:f)if(e.c==c)return e.row;
 static const uint8_t unknown[7]={31,17,1,2,4,0,4};return unknown;
}
inline bool ink(int x,int y,const char* text,int left,int top,int scale=2) {
 x-=left;y-=top;if(x<0||y<0||y>=7*scale)return false;
 unsigned i=unsigned(x/(6*scale));if(i>=strlen(text)||x%(6*scale)>=5*scale)return false;
 return glyph(text[i])[y/scale] & (1<<(4-(x%(6*scale))/scale));
}
inline uint16_t pixel(const View& v,int x,int y) {
 constexpr uint16_t W=0xffff,R=0xf800,G=0x07e0,B=0x001f,K=0;
 if(x==0||y==0||x==239||y==239)return W;
 char title[32],line[32];
 snprintf(title,sizeof title,"R%u %s",v.rotation,v.page==View::TEST?"RGB TEST":v.page==View::SENSOR?"SHT40":"SPI SHARED");
 if(ink(x,y,title,12,20))return W;
 if(ink(x,y,"0",4,4,1)||ink(x,y,"1",229,4,1)||ink(x,y,"2",229,229,1)||ink(x,y,"3",4,229,1))return W;
 if(v.page==View::TEST){
  if(y>=50&&y<130)return x<80?R:x<160?G:B;
  if(ink(x,y,"240X240",12,155)||ink(x,y,"RGB565 LE",12,182))return W;
  const char* gap=v.rotation==0?"GAP 0,80":v.rotation==1?"GAP 80,0":"GAP 0,0";
  return ink(x,y,gap,12,208)?W:K;
 }
 if(v.page==View::PROGRESS){
  snprintf(line,sizeof line,"PASS %03u/100",v.seq);
  if(ink(x,y,line,12,80)||ink(x,y,"WRITE READ",12,125)||ink(x,y,"SPI DONE",12,170))return W;
  return K;
 }
 if(!v.valid)return (ink(x,y,"NO SAMPLE",12,80)||ink(x,y,"CHECK I2C",12,125))?W:K;
 unsigned t=v.temperature_mC<0?unsigned(-v.temperature_mC):unsigned(v.temperature_mC);
 snprintf(line,sizeof line,"T %c%u.%u C",v.temperature_mC<0?'-':'+',t/1000,(t%1000)/100);
 if(ink(x,y,line,12,80))return W;
 snprintf(line,sizeof line,"RH %u.%u %%",unsigned(v.humidity_mPermille)/1000,(unsigned(v.humidity_mPermille)%1000)/100);
 return ink(x,y,line,12,125)?W:K;
}
struct Rotation { bool swap,mx,my; int gx,gy; };
inline Rotation rotation(unsigned r){switch(r&3){case 0:return {false,true,true,0,80};case 1:return {true,false,true,80,0};case 2:return {false,false,false,0,0};default:return {true,true,false,0,0};}}
}
// ---- components/hxdisplay/Tft.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include "esp_err.h"
#include "driver/spi_master.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "Graphics.h"
namespace hx {
constexpr int SCK=12,MOSI=11,MISO=10,TCS=5,CCS=13,DC=4,RST=6,BL=7;
constexpr spi_host_device_t HOST=SPI2_HOST;
esp_err_t init_bus();
class Tft {
 public:
  esp_err_t init();
  esp_err_t set_rotation(unsigned r);
  esp_err_t draw(const View& v);
  void backlight(bool on);
 private:
  esp_lcd_panel_handle_t panel_=nullptr;
  esp_lcd_panel_io_handle_t io_=nullptr;
  SemaphoreHandle_t done_=nullptr;
  uint16_t* strip_=nullptr;
};
}
// ---- components/hxdisplay/Tft.cpp ----
// SPDX-License-Identifier: MIT
#include "Tft.h"
#include <initializer_list>
#include "driver/gpio.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_vendor.h"
namespace hx {
esp_err_t init_bus(){
 for(int pin:{TCS,CCS}){ESP_ERROR_CHECK(gpio_set_level(gpio_num_t(pin),1));ESP_ERROR_CHECK(gpio_set_direction(gpio_num_t(pin),GPIO_MODE_OUTPUT));}
 ESP_ERROR_CHECK(gpio_set_level(gpio_num_t(BL),0));ESP_ERROR_CHECK(gpio_set_direction(gpio_num_t(BL),GPIO_MODE_OUTPUT));
 spi_bus_config_t cfg{};cfg.sclk_io_num=SCK;cfg.mosi_io_num=MOSI;cfg.miso_io_num=MISO;
 cfg.quadwp_io_num=-1;cfg.quadhd_io_num=-1;cfg.max_transfer_sz=240*16*2;
 return spi_bus_initialize(HOST,&cfg,SPI_DMA_CH_AUTO);
}
static bool done_cb(esp_lcd_panel_io_handle_t,esp_lcd_panel_io_event_data_t*,void* ctx){
 BaseType_t wake=pdFALSE;xSemaphoreGiveFromISR((SemaphoreHandle_t)ctx,&wake);return wake==pdTRUE;
}
void Tft::backlight(bool on){gpio_set_level(gpio_num_t(BL),on);}
esp_err_t Tft::init(){
 done_=xSemaphoreCreateBinary();strip_=(uint16_t*)heap_caps_malloc(240*16*2,MALLOC_CAP_DMA|MALLOC_CAP_INTERNAL);
 if(!done_||!strip_)return ESP_ERR_NO_MEM;
 esp_lcd_panel_io_spi_config_t io{};io.cs_gpio_num=TCS;io.dc_gpio_num=DC;io.spi_mode=0;io.pclk_hz=4000000;io.trans_queue_depth=1;io.lcd_cmd_bits=8;io.lcd_param_bits=8;io.on_color_trans_done=done_cb;io.user_ctx=done_;
 esp_err_t e=esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)HOST,&io,&io_);if(e!=ESP_OK)return e;
 esp_lcd_panel_dev_config_t dev{};dev.reset_gpio_num=RST;dev.rgb_ele_order=LCD_RGB_ELEMENT_ORDER_RGB;dev.data_endian=LCD_RGB_DATA_ENDIAN_LITTLE;dev.bits_per_pixel=16;
 if((e=esp_lcd_new_panel_st7789(io_,&dev,&panel_))!=ESP_OK)return e;
 if((e=esp_lcd_panel_reset(panel_))!=ESP_OK)return e;
 if((e=esp_lcd_panel_init(panel_))!=ESP_OK)return e;
 if((e=esp_lcd_panel_invert_color(panel_,true))!=ESP_OK)return e;
 if((e=set_rotation(0))!=ESP_OK)return e;
 return esp_lcd_panel_disp_on_off(panel_,true);
}
esp_err_t Tft::set_rotation(unsigned r){
 auto q=rotation(r);esp_err_t e=esp_lcd_panel_swap_xy(panel_,q.swap);if(e!=ESP_OK)return e;
 if((e=esp_lcd_panel_mirror(panel_,q.mx,q.my))!=ESP_OK)return e;
 return esp_lcd_panel_set_gap(panel_,q.gx,q.gy);
}
esp_err_t Tft::draw(const View& v){
 for(int y=0;y<240;y+=16){
  for(int j=0;j<16;++j)for(int x=0;x<240;++x)strip_[j*240+x]=pixel(v,x,y+j);
  esp_err_t e=esp_lcd_panel_draw_bitmap(panel_,0,y,240,y+16,strip_);if(e!=ESP_OK)return e;
  if(xSemaphoreTake(done_,pdMS_TO_TICKS(1000))!=pdTRUE){backlight(false);return ESP_ERR_TIMEOUT;}
 }
 backlight(true);return ESP_OK;
}
}
// ---- main/main.cpp ----
// SPDX-License-Identifier: MIT
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "Tft.h"
#include "Console.h"
#include "Sht40Logic.h"

static hx::Tft screen;
static bool sample(i2c_master_dev_handle_t dev,int& t,int& rh){
 uint8_t cmd=0xfd,b[6];esp_err_t e=i2c_master_transmit(dev,&cmd,1,100);
 if(e!=ESP_OK){ESP_LOGW("D03","SHT40 command %s",esp_err_to_name(e));return false;}
 vTaskDelay(pdMS_TO_TICKS(10));
 e=i2c_master_receive(dev,b,6,100);
 if(e!=ESP_OK){ESP_LOGW("D03","SHT40 read %s",esp_err_to_name(e));return false;}
 if(!decode_sht40(b,t,rh)){ESP_LOGW("D03","SHT40 CRC failed; discard sample");return false;}
 return true;
}
extern "C" void app_main(){
 init_console();ESP_ERROR_CHECK(hx::init_bus());ESP_ERROR_CHECK(screen.init());
 i2c_master_bus_config_t bus_cfg{};bus_cfg.i2c_port=I2C_NUM_0;bus_cfg.sda_io_num=GPIO_NUM_8;bus_cfg.scl_io_num=GPIO_NUM_9;bus_cfg.clk_source=I2C_CLK_SRC_DEFAULT;bus_cfg.glitch_ignore_cnt=7;bus_cfg.flags.enable_internal_pullup=true;
 i2c_master_bus_handle_t bus;ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg,&bus));
 i2c_device_config_t cfg{};cfg.dev_addr_length=I2C_ADDR_BIT_LEN_7;cfg.device_address=0x44;cfg.scl_speed_hz=100000;
 i2c_master_dev_handle_t sensor;ESP_ERROR_CHECK(i2c_master_bus_add_device(bus,&cfg,&sensor));
 ESP_LOGI("D03","READY PID3787 240x240 RGB565 LE; keys0..3 rotation, T test card, S live SHT40; keep SD socket empty");
 hx::View v;bool dirty=true;uint32_t last=0;v.page=hx::View::TEST;
 for(;;){
  char c=console_key();
  if(c>='0'&&c<='3'){v.rotation=unsigned(c-'0');ESP_ERROR_CHECK(screen.set_rotation(v.rotation));dirty=true;}
  if(c=='t'||c=='T'){v.page=hx::View::TEST;dirty=true;}
  if(c=='s'||c=='S'){v.page=hx::View::SENSOR;v.valid=false;last=0;dirty=true;}
  uint32_t now=uint32_t(xTaskGetTickCount()*portTICK_PERIOD_MS);
  if(v.page==hx::View::SENSOR&&(uint32_t(now-last)>=2000||last==0)){
   last=now;v.valid=sample(sensor,v.temperature_mC,v.humidity_mPermille);dirty=true;
   if(v.valid)ESP_LOGI("D03","sample valid t_mC=%d rh_mPermille=%d",v.temperature_mC,v.humidity_mPermille);
   else ESP_LOGW("D03","NO SAMPLE; old values hidden");
  }
  if(dirty){
   esp_err_t e=screen.draw(v);if(e!=ESP_OK){screen.backlight(false);ESP_LOGE("D03","STOP frame SPI=%s; no further jobs; DMA buffer retained; RESET after inspection",esp_err_to_name(e));return;}
   auto q=hx::rotation(v.rotation);ESP_LOGI("D03","frame submitted+SPI completed page=%d r=%u gap=%d,%d; optical result not checked",int(v.page),v.rotation,q.gx,q.gy);dirty=false;
  }
  vTaskDelay(pdMS_TO_TICKS(10));
 }
}
08 / 显示与总线实践

色卡、四方向、有效后失联分开验

作者真实IDF构建Exit0,CRC端点/错误向量、RGB565/边框与旋转表的原样header fixture通过;没有SHT40/TFT硬件或光学模型。以下实物检查仍pending,记录表未填结果。

  1. 启动先看R0色卡,核对左红中绿右蓝、完整白色边框、四角0/1/2/3。
  2. 依次发0、1、2、3,用板上排针作方位参考拍四张,记录gap和哪边朝上。
  3. 发S,记录真实T/RH与采样日志;发T回色卡。
  4. 冷启动缺模块:断电移除SHT40,再启动发S,应NO SAMPLE。该场景不能证明旧值撤销。
  5. 已有样本后失联:提前断电在SDA支路装SPST;有效样本后只断开信号开关,下次失败采样应NO SAMPLE。记录最后有效与首次失败时刻;闭合等下一轮,必要断电恢复。
做到这里,你应该看到
四方向照片、真实传感器样本、已有值后断线三个证据都要独立保存。
09 / 显示与总线实践

按现象定位,再决定改哪一项

只有背光:查VIN/BL之外的TC、DC、RST与SPI时钟;边缘裁切:查固定模块、窗口和gap;红蓝错:查RGB/BGR,杂色再查字节序。先固定一个变量,不把多项配置同时翻转。

SHT40无值:查0x44、SDA8/SCL9、模块供电和具体NACK/CRC日志。运行中只动预装信号开关,改供电和卡扣要断电。SPI超时后先留日志,RESET恢复;需要重新上传按F06操作BOOT/RESET,不停用欠压或看门狗。

前置E01解决SDK与构建、F07解决模块资料核对;下一步P08沿用同源显示组件和接线,再加入板载microSD,按两设备先分别通过的顺序验收。

参考:[2] Adafruit具体排针、电源、背光与SO用途[3] Adafruit ST7789固定1.11.0窗口和方向[4] IDF5.5.5 ST7789:RAMCTRL/色序/镜像/偏移[10] IDF5.5.5 S3新I2C主机API

BACK TO THE SOURCE

资料来源与版本说明

固定版本源与具体模块资料逐项核对;构建与主机逻辑检查通过,所有实板观察待测。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. PID3787产品与2022年11月修订
  2. Adafruit具体排针、电源、背光与SO用途
  3. Adafruit ST7789固定1.11.0窗口和方向
  4. IDF5.5.5 ST7789:RAMCTRL/色序/镜像/偏移
  5. IDF5.5.5 SPI LCD:DMA事务与完成回调
  6. DevKitC-1 v1.1排针
  7. Adafruit PID4885 SHT40模块
  8. Sensirion SHT4x1.1.2:0xFD、10ms与换算
  9. Sensirion Core0.7.3:CRC31/FF
  10. IDF5.5.5 S3新I2C主机API