CH567 SPI NOR 测试

最近编写了一个 CH567 SPI 读写测试的代码,测试的 SPI NOR 是 gd25q128e(特别注意:这款在Taobao上有假货,使用几次之后就无法读写,包括 READ ID 都无法响应)。这款 SPI 每个Page有256 Bytes ;每个 Sector有 4K Bytes; Block有 32K或者64K字节。

CH567 支持2个 SPI Host,这次测试的是 SPI0。

硬件连接:硬件连接:

引脚    
1 CS#PA12 3.3V8 VCC
2 MISOPA15 3.3V7 HOLD#/RESET#
3 WP#3.3V PA136 SCLK
4 VSSGND PA145 MOSI

对CH567的连接方法

一些常用的命令定义如下:

FunctionCommand NameByte 1Byte 2Byte 3Byte 4Byte 5
Void WriteEnable()Write Enable06H
Void WriteDisable()Write Disable04H
UINT8 ReadStatus1()Read Status Register-105H(S7-S0)(cont.)
UINT8 ReadStatus2()Read Status Register-235H(S15-S8)(cont.)
UINT8 ReadStatus3()Read Status Register-315H(S23-S16)(cont.)
Void WriteStatus1(UINT8)Write Status Register-101HS7-S0
Void WriteStatus2(UINT8)Write Status Register-231HS15-S8
Void WriteStatus3(UINT8)Write Status Register-311HS23-S16
Void ReadData(UINT32, UINT16, PUINT8)Read Data03HA23-A16A15-A8A7-A0(D7-D0)
Void PageProgram()Page Program02HA23-A16A15-A8A7-A0D7-D0
Void SectorErase()Sector Erase20HA23-A16A15-A8A7-A0
Void BlockErase32K()Block Erase (32K)52HA23-A16A15-A8A7-A0
Void BlockErase64K()Block Erase (64K)D8HA23-A16A15-A8A7-A0
Void ChipErase()Chip EraseC7H
UINT32 ReadID()Read Identification9FH(M7-M0)(JDID15-JDID8)(JDID7-
JDID0)
(cont.)

特别注意这颗SPI NOR 有三个8Bits的 Status寄存器。

测试如下三个操作:

1.读取 0 开始的 16字节内容;

2.擦除 0 开始的一个 Sector(4K);

3.在 0 开始写入 16字节内容。

/*
 ============================================================================
 Name        : main_SDIO_TF.c
 Author      : wch_cc
 Version     : V1.0.0
 Copyright   :
 Description : ²Ù×÷TF¿¨
 ============================================================================
 */
 
#include <stdio.h>
#include <string.h>
#include <nds32_isr.h>
#include <nds32_intrinsic.h>
#include "CH568SFR.H"
#include "MYdebug.H"
 
__attribute__ ((aligned(8))) UINT8  Sendbuff[512*2] ;       //8×Ö½Ú¶ÔÆë
__attribute__ ((aligned(8))) UINT8  Recvbuff[512*2] ;       //8×Ö½Ú¶ÔÆë
 
#define  GD25Q_CMD_WRITEENABLE   0x06
#define  GD25Q_CMD_WRITEDISABLE  0x04
#define  GD25Q_CMD_READSTATUS1   0x05
#define  GD25Q_CMD_READSTATUS2   0x35
#define  GD25Q_CMD_READSTATUS3   0x15
#define  GD25Q_CMD_WRITESTATUS1  0x01
#define  GD25Q_CMD_WRITESTATUS2  0x31
#define  GD25Q_CMD_WRITESTATUS3  0x11
#define  GD25Q_CMD_READDATA      0x03
#define  GD25Q_CMD_PAGEPROGRAM   0x02
#define  GD25Q_CMD_SECTORERASE   0x20
#define  GD25Q_CMD_BLOCKERASE32K 0x52
#define  GD25Q_CMD_BLOCKERASE64K 0xD8
#define  GD25Q_CMD_CHIPERASE     0xC7
#define  GD25Q_CMD_READID        0x9F
 
/********************************* 引脚定义 ************************************
*    PA12  <===========>  SCS0
*    PA13  <===========>  SCK0
*    PA14  <===========>  MOSI0
*    PA15  <===========>  MISO0
*******************************************************************************/
#define  SPI0_CS_LOW()        R32_PA_CLR |= 1<<12
#define  SPI0_CS_HIGH()       R32_PA_OUT |= 1<<12
 
/*******************************************************************************
* Function Name  : SPI_MASTER_INIT
* Description    : SPI0主机模式初始化
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void SPI_MASTER_INIT ( void )
{
        R8_SPI0_CTRL_MOD = RB_SPI_MOSI_OE|RB_SPI_SCK_OE;                              /* MOSI,SCK输出使能,主机模式,方式0 */
        R8_SPI0_CLOCK_DIV = 0x0a;                                                     /* 10分频,100/10=10M */
        R32_PA_DIR |= (1<<14 | 1<<13 | 1<<12);                                        /* MOSI(PA14),SCK0(PA13),SCS(PA12)为输出*/
        R32_PA_PU  |=  1<<12 ;
        R8_SPI0_CTRL_CFG &= ~RB_SPI_DMA_ENABLE;
}
 
/*******************************************************************************
* Function Name  : SPI0_Trans
* Description    : 发送一字节数据
* Input          : data -要发送的数据
* Output         : None
* Return         : None
*******************************************************************************/
void SPI0_Trans( UINT8 data )
{
 
//    R8_SPI0_CTRL_MOD &= ~RB_SPI_FIFO_DIR;
//    R8_SPI0_BUFFER = data;
//    while( !(R8_SPI0_INT_FLAG & RB_SPI_FREE) );
 
        R32_SPI0_FIFO = data;
        R16_SPI0_TOTAL_CNT = 0x01;
        while( R8_SPI0_FIFO_COUNT != 0 );                                           /* 等待数据发送完毕 */
}
 
/*******************************************************************************
* Function Name  : SPI0_Recv
* Description    : 接收一字节数据
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
UINT8 SPI0_Recv( void )
{
//    R8_SPI0_CTRL_MOD &= ~RB_SPI_FIFO_DIR;
//    R8_SPI0_BUFFER = 0xFF;                                //启动传输
//    while( !(R8_SPI0_INT_FLAG & RB_SPI_FREE) );
//    return ( R8_SPI0_BUFFER );
 
        UINT8 data;
        R32_SPI0_FIFO = 0xff;
        R16_SPI0_TOTAL_CNT = 0x01;
        while( R8_SPI0_FIFO_COUNT != 0 );                                           /* 等待数据回来 */
        data = R8_SPI0_BUFFER;
        return data;
}
 
/*******************************************************************************
* Function Name  : SPIFlash_ReadID
* Description    : SPI Flash读取芯片ID
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT32 SPIFlash_ReadID()
{
        UINT32  temp = 0;
 
        R32_PA_CLR |=  1<<12 ;
 
        SPI0_Trans(GD25Q_CMD_READID);    //读取ID命令
        temp = SPI0_Recv();
        temp = temp<<8;
        temp |= SPI0_Recv();
        temp = temp<<8;
        temp |= SPI0_Recv();
 
        R32_PA_OUT |=  1<<12 ;
 
        return temp;
}
 
/*******************************************************************************
* Function Name  : WriteEnable
* Description    : 写使能
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void WriteEnable( void )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITEENABLE );   //发送写使能命令
        SPI0_CS_HIGH();
}
 
/*******************************************************************************
* Function Name  : WriteDisable
* Description    : 关闭写
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void WriteDisable( void )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITEDISABLE );   //发送写禁止命令
        SPI0_CS_HIGH();
}
 
/*******************************************************************************
* Function Name  : ReadStatus1
* Description    : 读取状态寄存器1
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT8 ReadStatus1()
{
        UINT8 Status;
 
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_READSTATUS1 ); //发送读状态寄存器的命令
        Status = SPI0_Recv();      //读取状态寄存器
        SPI0_CS_HIGH();
 
        return Status;
}
 
/*******************************************************************************
* Function Name  : ReadStatus2
* Description    : 读取状态寄存器2
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT8 ReadStatus2()
{
        UINT8 Status;
 
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_READSTATUS2 ); //发送读状态寄存器的命令
        Status = SPI0_Recv();      //读取状态寄存器
        SPI0_CS_HIGH();
 
        return Status;
}
 
/*******************************************************************************
* Function Name  : ReadStatus3
* Description    : 读取状态寄存器3
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT8 ReadStatus3()
{
        UINT8 Status;
 
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_READSTATUS3 ); //发送读状态寄存器的命令
        Status = SPI0_Recv();      //读取状态寄存器
        SPI0_CS_HIGH();
 
        return Status;
}
 
/*******************************************************************************
* Function Name  : WriteStatus1
* Description    : 写状态寄存器1
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
void WriteStatus1( UINT8 Status )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITESTATUS1 );     //发送写状态寄存器的命令
        SPI0_Trans( Status );                     //发状态寄存器的数值
        SPI0_CS_HIGH();
 
        return ;
}
 
/*******************************************************************************
* Function Name  : WriteStatus2
* Description    : 写状态寄存器2
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
void WriteStatus2( UINT8 Status )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITESTATUS2 );     //发送写状态寄存器的命令
        SPI0_Trans( Status );                     //发状态寄存器的数值
        SPI0_CS_HIGH();
 
        return ;
}
 
/*******************************************************************************
* Function Name  : WriteStatus3
* Description    : 写状态寄存器3
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
void WriteStatus3( UINT8 Status )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITESTATUS3 );     //发送写状态寄存器的命令
        SPI0_Trans( Status );                     //发状态寄存器的数值
        SPI0_CS_HIGH();
 
        return ;
}
 
/*******************************************************************************
* Function Name  : ReadExternalFlash_SPI
* Description    : 读数据,注意:地址低8位必须为0
*******************************************************************************/
void ReadData( UINT32 StarAddr, UINT16 Len, PUINT8 RcvBuffer )
{
        UINT16 i;
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_READDATA);                                         //读命令'
        SPI0_Trans(((StarAddr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((StarAddr & 0xFFFF) >> 8));
        SPI0_Trans(StarAddr & 0xFF);
        for (i=0; i<Len; i++)
        {
            RcvBuffer[i]=SPI0_Recv();
        }
        SPI0_CS_HIGH();
        RcvBuffer[0]=0xFF;
}
 
/*******************************************************************************
* Function Name  : WaitExternalFlashIfBusy
* Description    : 等待芯片空闲(在执行Byte-Program, Sector-Erase, Block-Erase, Chip-Erase操作后)
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void WaitExternalFlashIfBusy( void )
{
        while ((ReadStatus1()&0x01) == 0x01 )
        {
            printf("Busy\n");
                ;    //等待直到Flash空闲
        }
}
/*******************************************************************************
* Function Name  : PageProgram
* Description    : 对一个page 进行写入
*******************************************************************************/
void PageProgram( UINT32 StarAddr, UINT16 Len, PUINT8 RcvBuffer )
{
        int i;
 
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(0x02);                                         //读命令
        SPI0_Trans(((StarAddr & 0xFFFFFF) >> 16));                //发送3字节地址
        SPI0_Trans(((StarAddr & 0xFFFF) >> 8));
        SPI0_Trans(StarAddr & 0xFF);
        for (i=0; i<Len; i++)
        {
                SPI0_Trans(RcvBuffer[i]);
        }
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();                               //等待写入结束
}
 
/*******************************************************************************
* Function Name  : SectorErase
* Description    : 擦除4K Flash  擦除一个扇区
* Input          : Dst_Addr 0-1 ffff ffff ,清除任意地址所在的扇区
* Output         : None
* Return         : None
*******************************************************************************/
void SectorErase( UINT32 Dst_Addr )
{
        WriteEnable();
        WaitExternalFlashIfBusy();
        printf("SectorErase Status1:0x%01x\n",ReadStatus1());
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_SECTORERASE);                                      //扇区擦除命令
        SPI0_Trans(((Dst_Addr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((Dst_Addr & 0xFFFF) >> 8));
        SPI0_Trans(Dst_Addr & 0xFF);
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
/*******************************************************************************
* Function Name  : Block32KErase
* Description    : 擦除32K Flash
* Input          : Dst_Addr 0-1 ffff ffff ,清除任意地址所在的扇区
* Output         : None
* Return         : None
*******************************************************************************/
void Block32KErase( UINT32 Dst_Addr )
{
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_BLOCKERASE32K);                                      //扇区擦除命令
        SPI0_Trans(((Dst_Addr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((Dst_Addr & 0xFFFF) >> 8));
        SPI0_Trans(Dst_Addr & 0xFF);
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
/*******************************************************************************
* Function Name  : Block64KErase
* Description    : 擦除64K Flash
* Input          : Dst_Addr 0-1 ffff ffff ,清除任意地址所在的扇区
* Output         : None
* Return         : None
*******************************************************************************/
void Block64KErase( UINT32 Dst_Addr )
{
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_BLOCKERASE64K);                                      //扇区擦除命令
        SPI0_Trans(((Dst_Addr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((Dst_Addr & 0xFFFF) >> 8));
        SPI0_Trans(Dst_Addr & 0xFF);
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
/*******************************************************************************
* Function Name  : ChipErase
* Description    : 擦除整片
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void ChipErase()
{
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_CHIPERASE);     //整片擦除
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
int main()
{
 
        Interrupt_init( 1<<INT_ID_SDC );     /* 系统总中断开启 */
        SysClkInit();           /* 初始化系统时钟 */
        mInitSTDIO_UR3();       /* 调试信息初始化 printf调用 */
        SPI_MASTER_INIT();
        mDelaymS(200);
        printf("START SPI FLASH\n");
 
        // 读取 ID
        printf("id:0x%04x\n",SPIFlash_ReadID() );
         
        // 度状态寄存器
        printf("Status1:0x%01x\n",ReadStatus1());
        printf("Status2:0x%01x\n",ReadStatus2());
        printf("Status3:0x%01x\n",ReadStatus3());
         
        // 在所有的写入操作之前都要设置起来 WEL
        WriteEnable();
        WaitExternalFlashIfBusy();
        WriteStatus1(0x02);
        WaitExternalFlashIfBusy();
 
        WriteEnable();
        WaitExternalFlashIfBusy();
        WriteStatus3(0x00);
        WaitExternalFlashIfBusy();
         
        // 输出 Status Register
        printf("Status1:0x%01x\n",ReadStatus1());
        printf("Status2:0x%01x\n",ReadStatus2());
        printf("Status3:0x%01x\n",ReadStatus3());
         
        // 使用 16字节缓冲进行测试
        UINT8 Buffer[16];
         
        // 给缓冲区赋初始值便于观察
        Buffer[0]=0x12; Buffer[1]=0x34; Buffer[2]=0x56; Buffer[3]=0x78;
        Buffer[4]=0x9A; Buffer[5]=0xBC; Buffer[6]=0xDE; Buffer[7]=0xF0;
         
        // 1. 读取测试
        // 读取 SPI 起始处  16字节
        printf("Read Test\n");
        ReadData(0,16,&Buffer[0]);
        UINT16 i;
        for (i=0;i<16;i++) {
                printf("%X ",Buffer[i]);
        }
        printf("\n");
         
        // 2. Sector(4K Bytes)擦除测试
        printf("Sector Erase\n");
        SectorErase(0);
        ReadData(0,16,Buffer);
        for (i=0;i<16;i++) {
                printf("%X ",Buffer[i]);
        }
        printf("\n");
         
        // 3. PagePrgram (最大 一次写入256Bytes)测试
        printf("Page Program\n");
        Buffer[0]=0x12; Buffer[1]=0x34; Buffer[2]=0x56; Buffer[3]=0x78;
        Buffer[4]=0x9A; Buffer[5]=0xBC; Buffer[6]=0xDE; Buffer[7]=0xF0;
        PageProgram(0,8,Buffer);
        for (i=0;i<16;i++) {
                printf("%X ",Buffer[i]);
        }
        printf("\n");
        return 0;
}

冷知识:更换SoC后要重刷Firmware

最让 BIOS 工程师头大的问题就是要回答BIOS范畴之外的问题,例如:Hardware issue, OS issue 以及BIOS 之外的 Firmware issue。今天介绍的一个就是典型的别人会找BIOS工程师,但是真不归BIOS 管的问题。

在研发和调试的时候,非常特殊情况下我们需要更换SoC进行测试。更换之后务必重刷Firmware。这是因为在上电自检的时候,SoC中初始化代码如果发现 SoC损坏,为了避免更严重的问题(例如,SoC损坏之后异常发热有可能导致火灾),Firmware 会记录这个问题,下次再开机的时候先于BIOS执行的Firmware如果看到对应的标记会自动断电,在外界看起来更像是“SoC不上电”。

因此,在这种情况下,单纯更换SoC之后问题可能仍然存在,在重新刷写Firmware之后问题就会完全消失。

碰到上述问题时,经常有人将损坏SoC主板上的Firmware通过烧录器 Dump 为文件,将这个文件烧写到其他工作正常的设备,然后惊奇的发现之前工作正常的设备烧写之后也出现了故障设备同样的问题。接下来,他们会提出“请BIOS工程师解释一下”……..

有了上面的知识,下次遇到这种情况就可以直接告知原因。如果对方仍然想了解根本原因,只能联系SoC厂家了。

FireBeelte 显示环境声音频谱

前一段入手了Fermion: 全向MEMS麦克风模块(SEN0487),这款模块非常简单能够直接输出模拟信号。简单之处在于无需外部元件进行放大,当没有检测到声音时,输出电压在1.5V左右浮动,在说话时通过ADC采样可以很容易看到声音的波形。

首先跑了一下WIKI 给出的示例文件,使用 Arduino 自带的串口绘图工具查看了一下波形:

测试视频可以在B站看到:

可以看到内部自带了滤波,敏感度也不错。

接下来,配合之前多次使用的 12864 OLED制作一个频谱显示装置。代码参考 https://github.com/s-marley/ESP32_FFT_VU 了。硬件部分除了供电的 VCC和GND, 只需要将模块输出连接到A0(IO36)即可。

#include &lt;arduinoFFT.h>
#include "DFRobot_OLED12864.h"
 
// 12864 OLED 配置
#define I2C_addr 0x3c
#define pin_SPI_cs D2
 
#define SAMPLES         1024          // Must be a power of 2
#define SAMPLING_FREQ   40000         // Hz, must be 40000 or less due to ADC conversion time. Determines maximum frequency that can be analysed by the FFT Fmax=sampleF/2.
#define AMPLITUDE       1000          // Depending on your audio source level, you may need to alter this value. Can be used as a 'sensitivity' control.
#define AUDIO_IN_PIN    A0            // Signal in on this pin
 
#define NOISE           500           // Used as a crude noise filter, values below this are ignored
const uint8_t kMatrixWidth = 16;                          // Matrix width
const uint8_t kMatrixHeight = 16;                         // Matrix height
 
#define NUM_BANDS       16            // To change this, you will need to change the bunch of if statements describing the mapping from bins to bands
 
#define BAR_WIDTH      (kMatrixWidth  / (NUM_BANDS - 1))  // If width >= 8 light 1 LED width per bar, >= 16 light 2 LEDs width bar etc
#define TOP            (kMatrixHeight - 0)                // Don't allow the bars to go offscreen
 
DFRobot_OLED12864 OLED(I2C_addr, pin_SPI_cs);
 
// Sampling and FFT stuff
unsigned int sampling_period_us;
byte peak[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // The length of these arrays must be >= NUM_BANDS
int oldBarHeights[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int bandValues[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
double vReal[SAMPLES];
double vImag[SAMPLES];
unsigned long newTime;
 
arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
 
void setup() {
  Serial.begin(115200);
 
  OLED.init();
 // OLED.flipScreenVertically();
 
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));
}
 
unsigned long int Elsp1;
int h[16];
 
void loop() {
 
  // Reset bandValues[]
  for (int i = 0; i &lt; NUM_BANDS; i++) {
    bandValues[i] = 0;
  }
 
  // Sample the audio pin
  for (int i = 0; i &lt; SAMPLES; i++) {
    newTime = micros();
    vReal[i] = analogRead(AUDIO_IN_PIN); // A conversion takes about 9.7uS on an ESP32
    vImag[i] = 0;
    while ((micros() - newTime) &lt; sampling_period_us) {
      /* chill */
    }
  }
 
  // Compute FFT
  FFT.DCRemoval();
  FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(FFT_FORWARD);
  FFT.ComplexToMagnitude();
 
  // Analyse FFT results
  for (int i = 2; i &lt; (SAMPLES / 2); i++) {    // Don't use sample 0 and only first SAMPLES/2 are usable. Each array element represents a frequency bin and its value the amplitude.
    if (vReal[i] > NOISE) {                    // Add a crude noise filter
 
      //16 bands, 12kHz top band
      if (i &lt;= 2 )           bandValues[0]  += (int)vReal[i];
      if (i > 2   &amp;&amp; i &lt;= 3  ) bandValues[1]  += (int)vReal[i];
      if (i > 3   &amp;&amp; i &lt;= 5  ) bandValues[2]  += (int)vReal[i];
      if (i > 5   &amp;&amp; i &lt;= 7  ) bandValues[3]  += (int)vReal[i];
      if (i > 7   &amp;&amp; i &lt;= 9  ) bandValues[4]  += (int)vReal[i];
      if (i > 9   &amp;&amp; i &lt;= 13 ) bandValues[5]  += (int)vReal[i];
      if (i > 13  &amp;&amp; i &lt;= 18 ) bandValues[6]  += (int)vReal[i];
      if (i > 18  &amp;&amp; i &lt;= 25 ) bandValues[7]  += (int)vReal[i];
      if (i > 25  &amp;&amp; i &lt;= 36 ) bandValues[8]  += (int)vReal[i];
      if (i > 36  &amp;&amp; i &lt;= 50 ) bandValues[9]  += (int)vReal[i];
      if (i > 50  &amp;&amp; i &lt;= 69 ) bandValues[10] += (int)vReal[i];
      if (i > 69  &amp;&amp; i &lt;= 97 ) bandValues[11] += (int)vReal[i];
      if (i > 97  &amp;&amp; i &lt;= 135) bandValues[12] += (int)vReal[i];
      if (i > 135 &amp;&amp; i &lt;= 189) bandValues[13] += (int)vReal[i];
      if (i > 189 &amp;&amp; i &lt;= 264) bandValues[14] += (int)vReal[i];
      if (i > 264          ) bandValues[15] += (int)vReal[i];
    }
  }
 
  // Process the FFT data into bar heights
  for (byte band = 0; band &lt; NUM_BANDS; band++) {
 
    // Scale the bars for the display
    int barHeight = bandValues[band] / AMPLITUDE;
    if (barHeight > TOP) barHeight = TOP;
 
    // Small amount of averaging between frames
    barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2;
 
    // Move peak up
    if (barHeight > peak[band]) {
      peak[band] = min(TOP, barHeight);
    }
    h[band] = barHeight;
 
    // Save oldBarHeights for averaging later
    oldBarHeights[band] = barHeight;
 
  }
 
  if (millis() - Elsp1 > 60) {
    for (byte band = 0; band &lt; NUM_BANDS; band++)
      if (peak[band] > 0) peak[band] -= 1;
       
    Elsp1 = millis();
  }
  /*
       //直接输出 FFT 结果
       for (int i=0;i&lt;16;i++) {
         if (bandValues[i]&lt;16) {Serial.print(" ");}
         Serial.print(bandValues[i],HEX);
         Serial.print(" ");
       }
       Serial.println("");
  */
 
  // 擦除上一次
  OLED.clear();  
  for (int i = 0; i &lt; 16; i++) {
   OLED.fillRect(8 * (16-i), 0, 8, 4 * h[i]);
   OLED.fillRect(8 * (16-i), peak[i]*4, 8, 3);
  }
   
  for (int i = 0; i &lt; 16; i++) {  
    Serial.print(h[i], HEX);
    Serial.print("");
  }
  Serial.println("");
  /*
  for (int i = 0; i &lt; 16; i++) {  
    Serial.print(peak[i], HEX);
    Serial.print("");
  }
  Serial.println("");
  */
 
  // 显示当前
  OLED.display();
}

最终成品的测试视频:

参考:

  1. https://www.dfrobot.com.cn/goods-3220.html
  2. https://wiki.dfrobot.com.cn/_SKU_SEN0487_Fermion_MEMS_Microphone_Sensor

作者ziv2013发布于分类Funny

发表回复

BellWin UP520US USB Power Splitter 介绍

最近在研究一款USB控制的排插:BellWin 出品的带有USB接口的排插(USB Power Splitter)型号是:UP520US。它带有五个插孔,一个用于控制插孔供电的USB接口和一个供电开关。这款排插不是给家用设计的,上面的的插孔和插头都是专门为机房这种特别场合设计的,在家用环境中必须使用转接头才能工作。

电器特性如下【参考1】:

输入:220V,20A

输入频率:50/60Hz

功耗:5W

每一路最高可支持15A,五组最大支持输出20A

工作温度:0-25℃

接口:IEC60320 C13

认证:RoHS,IPC,CUS

首先,测试一下它自带的控制程序。

用户可以通过下面的按钮控制每一路的开关。

接下来,使用 USB Package Viewer 设备抓取USB 数据包。USB Package Viewer(简称UPV)是一款国产的USB数据逻辑分析仪,能够抓取从 Low Speed 到 High Speed的数据包。有兴趣的朋友可以在【参考2】看到之前我的开箱视频。这个仪器有一个比较大的优点是能够实时显示,这个在分析研究数据量不大的USB设备时非常有用。比如,上位机进行一个动作立刻能够在分析软件上看到数据,这样便于对应数据和操作。

从设备管理器上也可以看出,这个设备使用USB HID 协议。

按下试验软件上的开关两次之后,在UPV中可以看到抓到了2个数据包:

经过多次试验,总结数据如下:

操作抓到的数据,总长度64Bytes
Port1 设置为 OFF0B 00 00 00 00 00 00 5A……5A
Port2 设置为OFF0B 00 00 00 00 01 00 5A……5A
Port3 设置为OFF0B 00 00 00 00 02 00 5A……5A
Port4 设置为OFF0B 00 00 00 00 03 00 5A……5A
Port5 设置为OFF0B 00 00 00 00 04 00 5A……5A
Port1 设置为 ON0B 00 00 00 00 00 01 5A……5A
Port2 设置为ON0B 00 00 00 00 01 01 5A……5A
Port3 设置为ON0B 00 00 00 00 02 01 5A……5A
Port4 设置为ON0B 00 00 00 00 03 01 5A……5A
Port5 设置为ON0B 00 00 00 00 04 01 5A……5A

需要注意的是,上面的数据末端,使用0x5A 填充,实际上使用任意数据进行填充都可以, USB 排插以前面的有效数据为准。

根据上述表格,很容易编写一个控制这个排插开关的代码(VS2019):

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <setupapi.h>
 
extern "C" {
 
    void __stdcall
        HidD_GetHidGuid(
            OUT   LPGUID   HidGuid
        );
 
    typedef struct _HIDD_ATTRIBUTES {
        ULONG   Size; // = sizeof (struct _HIDD_ATTRIBUTES)
 
                      //
        // Vendor ids of this hid device
        //
        USHORT  VendorID;
        USHORT  ProductID;
        USHORT  VersionNumber;
 
        //
        // Additional fields will be added to the end of this structure.
        //
    } HIDD_ATTRIBUTES, * PHIDD_ATTRIBUTES;
 
    BOOLEAN __stdcall
        HidD_GetAttributes(
            IN  HANDLE              HidDeviceObject,
            OUT PHIDD_ATTRIBUTES    Attributes
        );
 
    BOOLEAN __stdcall
        HidD_SetFeature(
            _In_    HANDLE   HidDeviceObject,
            _In_reads_bytes_(ReportBufferLength) PVOID ReportBuffer,
            _In_    ULONG    ReportBufferLength
        );
}
 
#pragma comment( lib, "hid.lib" )
#pragma comment( lib, "setupapi.lib" )
 
void SetAll(HANDLE hUsb, bool AllOn)
{
    BOOL Result;
    UCHAR WriteReportBuffer[65];
 
    for (int i = 0; i < 8; i++) {
        WriteReportBuffer[i] = 0x00;
    }
    for (int i = 8; i < 65; i++) {
        WriteReportBuffer[i] = 0x5a;
    }
 
    WriteReportBuffer[1] = 0x0b;
    for (byte i = 0; i < 5; i++) {
        WriteReportBuffer[6] = i;
        if (AllOn) {
            WriteReportBuffer[7] = 0x01;
        }
 
        DWORD lpNumberOfBytesWritten;
 
        Result = WriteFile(hUsb,
            WriteReportBuffer,
            65,
            &lpNumberOfBytesWritten,
            NULL);
        //printf("Written %d bytes Result [%d]\n", lpNumberOfBytesWritten, Result);
    }
 
 
}
 
int main()
{
    GUID HidGuid;
    BOOL Result;
    int  counter = -1;
 
 
    HidD_GetHidGuid(&HidGuid);
    printf("HID GUID: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n"
        , HidGuid.Data1, HidGuid.Data2, HidGuid.Data3
        , HidGuid.Data4[0], HidGuid.Data4[1], HidGuid.Data4[2], HidGuid.Data4[3], HidGuid.Data4[4]
        , HidGuid.Data4[5], HidGuid.Data4[6], HidGuid.Data4[7]);
 
    HDEVINFO hDevInfo = SetupDiGetClassDevs(&HidGuid, NULL, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (INVALID_HANDLE_VALUE != hDevInfo)
    {
        SP_DEVICE_INTERFACE_DATA strtInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) };
        for (DWORD index = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &HidGuid, index, &strtInterfaceData); ++index)
        {
 
            char buf[1000];
            SP_DEVICE_INTERFACE_DETAIL_DATA& strtDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA&)buf[0];
            strtDetailData.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
            if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &strtInterfaceData, &strtDetailData, _countof(buf), NULL, NULL))
            {
                printf("[%d] path: %ls\n", index, strtDetailData.DevicePath);
 
                HANDLE hUsb = CreateFile(strtDetailData.DevicePath,
                    NULL, FILE_SHARE_WRITE,
                    NULL, OPEN_EXISTING,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
 
                HIDD_ATTRIBUTES strtAttrib = { sizeof(HIDD_ATTRIBUTES) };
                Result = HidD_GetAttributes(hUsb, &strtAttrib);
                CloseHandle(hUsb);
 
                if (TRUE == Result)
                {
                    if ((0x04D8 == strtAttrib.VendorID) &&
                        (0xFEDC == strtAttrib.ProductID))       //ÕÒµ½ÎÒÃÇ×Ô¼ºµÄÉ豸
                    {
                        printf("VendorID : %hX\n", strtAttrib.VendorID);
                        printf("ProductID: %hX\n", strtAttrib.ProductID);
                        printf("VerNumber: %hX\n", strtAttrib.VersionNumber);
 
                        hUsb = CreateFile(strtDetailData.DevicePath,
                            GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE,
                            NULL, OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL, NULL);
 
                        //Switch(hUsb); GetData(hUsb);
                        SetAll(hUsb,TRUE);
                        Sleep(3000);
                        SetAll(hUsb, FALSE);
 
                        CloseHandle(hUsb);
 
                    }//if ((0x8888==strtAttrib.VendorID) &&
                }            //if(TRUE==Result)
            } // if( SetupDiGetDeviceInterfaceDetail(hDevInfo,&strtInterfaceData,&strtDetailData,_countof(buf),NULL,NULL) )
        }    //for( DWORD index=0;
 
        if (GetLastError() != ERROR_NO_MORE_ITEMS)
        {
            printf("No more items!\n");
        }
 
        SetupDiDestroyDeviceInfoList(hDevInfo);
 
    }  //if( INVALID_HANDLE_VALUE != hDevInfo )
    system("PAUSE");
    return 0;
}

这个代码没有使用厂家提供的API (厂家提供了 DLL)而是直接对 HID设备编程,这样做的好处是无需外挂文件,一个 EXE 都可以搞定。坏处是,没有实现厂家API 提供的诸如取得序列号这样的功能,如果有需求还需要另外研究编写。但是无论如何,能够实现控制端口开关已经足够了。

综上所述:BellWin的UP520US是一款可以通过USB控制的排插,满足实验室安规要求。有需要的朋友可以进一步研究。

参考:

  1. http://powersplitter.bellwin.com.tw/UP520US.html
  2. https://www.bilibili.com/video/BV1CG4y1H7Xk/ USBPacketViewer

UEFI Shell 下 SPD 读取工具

最近需要读取内存条的 SPD 信息,于是研究了一下,在 CloverBootloader 项目中【参考1】找到了一份代码。经过修改后分析出来一个独立的代码, 实体机上测试如下:

Shell 下读取 SPD 测试
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/PciIo.h>
#include <IndustryStandard/Pci22.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/UefiBootServicesTableLib.h>
 
#include "memvendors.h"
#include "spd.h"
 
// Intel SMB reg offsets
#define SMBHSTSTS 0
#define SMBHSTCNT 2
#define SMBHSTCMD 3
#define SMBHSTADD 4
#define SMBHSTDAT 5
#define SMBHSTDAT1 6
#define SBMBLKDAT 7
// MCP and nForce SMB reg offsets
#define SMBHPRTCL_NV 0 /* protocol, PEC */
#define SMBHSTSTS_NV 1 /* status */
#define SMBHSTADD_NV 2 /* address */
#define SMBHSTCMD_NV 3 /* command */
#define SMBHSTDAT_NV 4 /* 32 data registers */
//
 
// XMP memory profile
#define SPD_XMP_SIG1 176
#define SPD_XMP_SIG1_VALUE 0x0C
#define SPD_XMP_SIG2 177
#define SPD_XMP_SIG2_VALUE 0x4A
#define SPD_XMP_PROFILES 178
#define SPD_XMP_VERSION 179
#define SPD_XMP_PROF1_DIVISOR 180
#define SPD_XMP_PROF1_DIVIDEND 181
#define SPD_XMP_PROF2_DIVISOR 182
#define SPD_XMP_PROF2_DIVIDEND 183
#define SPD_XMP_PROF1_RATIO 186
#define SPD_XMP_PROF2_RATIO 221
 
 
#define SPD_XMP20_SIG1 0x180
#define SPD_XMP20_SIG2 0x181
#define SPD_XMP20_PROFILES 0x182
#define SPD_XMP20_VERSION 0x183
/* 0x189 */
#define SPD_XMP20_PROF1_MINCYCLE 0x18C
#define SPD_XMP20_PROF1_FINEADJUST 0x1AF
/* 0x1B8 */
#define SPD_XMP20_PROF2_MINCYCLE 0x1BB
#define SPD_XMP20_PROF2_FINEADJUST 0x1DE
/* 0x1E7 */
 
BOOLEAN       smbIntel;
UINT8           smbPage;
 
/** Read one byte from i2c, used for reading SPD */
UINT8 smb_read_byte(UINT32 base, UINT8 adr, UINT16 cmd)
{
        //   INTN l1, h1, l2, h2;
        UINT64 t;
        UINT8 page;
        UINT8 c;
        //  UINT8 p;
 
        if (smbIntel)
        {
                IoWrite8(base + SMBHSTSTS, 0x1f);               // reset SMBus Controller (set busy)
                IoWrite8(base + SMBHSTDAT, 0xff);
                t=0;
                while ( IoRead8(base + SMBHSTSTS) & 0x01)     // wait until host is not busy
                {
                        if (t > 5)
                        {
                                gBS->Stall(1000000UL);
                                Print(L"host is busy for too long for byte %2hhX:%d!\n", adr, cmd);
                                return 0xFF;                  // break
                        }
                        t++;
                }
 
                page = (cmd >> 8) & 1;
                if (page != smbPage)
                {
                        // p = 0xFF;
                        IoWrite8(base + SMBHSTCMD, 0x00);
                        IoWrite8(base + SMBHSTADD, 0x6C + (page << 1)); // Set SPD Page Address
#if 0
                        IoWrite8(base + SMBHSTCNT, 0x48); // Start + Byte Data Write
                        // Don't use "Byte Data Write" because status goes from 0x41 (Busy) -> 0x44 (Error)
#else
                        IoWrite8(base + SMBHSTCNT, 0x40); // Start + Quick Write
                        // status goes from 0x41 (Busy) -> 0x42 (Completed)
#endif
                        smbPage = page;
 
                        t=0;
                        while (!( (c=IoRead8(base + SMBHSTSTS)) & 0x02))    // wait until command finished
                        {
                                gBS->Stall(1000000UL);
                                if (c & 4)
                                {
                                        Print(L"spd page change error for byte %2hhX:%d!\n", adr, cmd);
                                        break;
                                }
                                if (t > 5)
                                {
                                        Print(L"spd page change taking too long for byte %2hhX:%d!\n", adr, cmd);
                                        break;                                  // break after 5ms
                                }
                                t++;
                        }
                        return smb_read_byte(base, adr, cmd);
                }
 
                // p = 0xFF;
                IoWrite8(base + SMBHSTCMD, (UINT8)(cmd & 0xFF)); // SMBus uses 8 bit commands
                IoWrite8(base + SMBHSTADD, (adr << 1) | 0x01 ); // read from spd
                IoWrite8(base + SMBHSTCNT, 0x48 ); // Start + Byte Data Read
                // status goes from 0x41 (Busy) -> 0x42 (Completed) or 0x44 (Error)
 
                t=0;
                while (!( (c=IoRead8(base + SMBHSTSTS)) & 0x02))    // wait until command finished
                {
                        gBS->Stall(1000000UL);
                        if (c & 4)
                        {
                                // This alway happens when trying to read the memory type (cmd 2) of an empty slot
                                // Print(L"spd byte read error for byte %2hhX:%d!\n", adr, cmd);
                                break;
                        }
                        if (t > 5)
                        {
                                // if (cmd != 2)
                                Print(L"spd byte read taking too long for byte %2hhX:%d!\n", adr, cmd);
                                break;                                  // break after 5ms
                        }
                        t++;
                }
                return IoRead8(base + SMBHSTDAT);
        }
        else
        {
 
        }
}
 
/* SPD i2c read optimization: prefetch only what we need, read non prefetcheable bytes on the fly */
#define READ_SPD(spd, base, slot, x) spd[x] = smb_read_byte(base, 0x50 + slot, x)
 
 
/** Read from smbus the SPD content and interpret it for detecting memory attributes */
STATIC void read_smb(EFI_PCI_IO_PROTOCOL *PciIo, UINT16 vid, UINT16 did)
{
        //  EFI_STATUS  Status;
        UINT8       i;// spd_size, spd_type;
        UINT8       j;
        UINT32          base, mmio, hostc;
        UINT16          Command;
        //RAM_SLOT_INFO*  slot;
        //XBool         fullBanks;
        UINT8*          spdbuf;
//  UINT16          vid, did;
 
        UINT8                  TotalSlotsCount;
 
        smbPage = 0; // valid pages are 0 and 1; assume the first page (page 0) is already selected
//  vid = gPci->Hdr.VendorId;
//  did = gPci->Hdr.DeviceId;
 
        /*Status = */
        PciIo->Pci.Read (
                PciIo,
                EfiPciIoWidthUint16,
                PCI_COMMAND_OFFSET,
                1,
                &Command
        );
 
        Command |= 1;
        /*Status = */
        PciIo->Pci.Write (
                PciIo,
                EfiPciIoWidthUint16,
                PCI_COMMAND_OFFSET,
                1,
                &Command
        );
 
        Print(L"SMBus CmdReg: 0x%X\n", Command);
 
        /*Status = */
        PciIo->Pci.Read (
                PciIo,
                EfiPciIoWidthUint32,
                0x10,
                1,
                &mmio
        );
        if (vid == 0x8086)
        {
                /*Status = */PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0x20,
                        1,
                        &base
                );
 
                base &= 0xFFFE;
                smbIntel = TRUE;
        }
        else
        {
                /*Status = */PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0x24, // iobase offset 0x24 on MCP
                        1,
                        &base
                );
                base &= 0xFFFC;
                smbIntel = FALSE;
        }
        /*Status = */PciIo->Pci.Read (
                PciIo,
                EfiPciIoWidthUint32,
                0x40,
                1,
                &hostc
        );
 
 
        Print(L"Scanning SMBus [%04X:%04X], mmio: 0x%X, ioport: 0x%X, hostc: 0x%X\n",
              vid, did, mmio, base, hostc);
 
        // needed at least for laptops
        //fullBanks = (gDMI->MemoryModules == gDMI->CntMemorySlots);
 
        spdbuf = AllocateZeroPool(MAX_SPD_SIZE);
 
        TotalSlotsCount = 8; //MAX_RAM_SLOTS;  -- spd can read only 8 slots
        Print(L"Slots to scan [%d]...\n", TotalSlotsCount);
        for (i = 0; i <  TotalSlotsCount; i++)
        {
                //<==
                ZeroMem(spdbuf, MAX_SPD_SIZE);
                READ_SPD(spdbuf, base, i, SPD_MEMORY_TYPE);
                if (spdbuf[SPD_MEMORY_TYPE] == 0xFF)
                {
                        //Print(L"SPD[%d]: Empty\n", i);
                        continue;
                }
                else if (spdbuf[SPD_MEMORY_TYPE] == 0)
                {
                        // First 0x40 bytes of DDR4 spd second page is 0. Maybe we need to change page, so do that and retry.
                        Print(L"SPD[%d]: Got invalid type %d @0x%X. Will set page and retry.\n", i, spdbuf[SPD_MEMORY_TYPE], 0x50 + i);
                        smbPage = 0xFF; // force page to be set
                        READ_SPD(spdbuf, base, i, SPD_MEMORY_TYPE);
                        
                }
 
                // Copy spd data into buffer
                Print(L"SPD[%d]: Type %d @0x%X\n", i, spdbuf[SPD_MEMORY_TYPE], 0x50 + i);
                 for (j=0;j<16;j++) {
                         READ_SPD(spdbuf, base, i, j);
                         Print(L"%X ",spdbuf[j]);
                        }
                 Print(L"\n");
        } // for
        if (smbPage != 0)
        {
                READ_SPD(spdbuf, base, 0, 0); // force first page when we're done
        }
}
 
void ScanSPD()
{
        EFI_STATUS            Status;
        EFI_HANDLE            *HandleBuffer = NULL;
        EFI_PCI_IO_PROTOCOL   *PciIo = NULL;
        UINTN                 HandleCount;
        UINTN                 Index;
        PCI_TYPE00            gPci;
 
        // Scan PCI handles
        Status = gBS->LocateHandleBuffer (
                         ByProtocol,
                         &gEfiPciIoProtocolGuid,
                         NULL,
                         &HandleCount,
                         &HandleBuffer
                 );
 
        if (!EFI_ERROR(Status))
        {
                for (Index = 0; Index < HandleCount; ++Index)
                {
                        Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiPciIoProtocolGuid, (void **)&PciIo);
                        if (!EFI_ERROR(Status))
                        {
                                // Read PCI BUS
                                //PciIo->GetLocation (PciIo, &Segment, &Bus, &Device, &Function);
                                Status = PciIo->Pci.Read (
                                                 PciIo,
                                                 EfiPciIoWidthUint32,
                                                 0,
                                                 sizeof (gPci) / sizeof (UINT32),
                                                 &gPci
                                         );
                                //SmBus controller has class = 0x0c0500
                                if ((gPci.Hdr.ClassCode[2] == 0x0c) && (gPci.Hdr.ClassCode[1] == 5)
                                                && (gPci.Hdr.ClassCode[0] == 0) && (gPci.Hdr.VendorId == 0x8086 || gPci.Hdr.VendorId == 0x10DE))
                                {
                                        Print(L"SMBus device : %04hX %04hX class=%02X%02X%02X status=%r\n",
                                              gPci.Hdr.VendorId,
                                              gPci.Hdr.DeviceId,
                                              gPci.Hdr.ClassCode[2],
                                              gPci.Hdr.ClassCode[1],
                                              gPci.Hdr.ClassCode[0],
                                              Status
                                             );
                                        read_smb(PciIo, gPci.Hdr.VendorId, gPci.Hdr.DeviceId);
                                }
                        }
                }
        }
 
}
 
/**
  UEFI application entry point which has an interface similar to a
  standard C main function.
 
  The ShellCEntryLib library instance wrappers the actual UEFI application
  entry point and calls this ShellAppMain function.
 
  @param  ImageHandle  The image handle of the UEFI Application.
  @param  SystemTable  A pointer to the EFI System Table.
 
  @retval  0               The application exited normally.
  @retval  Other           An error occurred.
 
**/
INTN
EFIAPI
ShellAppMain (
        IN UINTN Argc,
        IN CHAR16 **Argv
)
{
        ScanSPD();
        return EFI_SUCCESS;
}

完整代码和EFI 下载:

ReadSPD下载

参考:

1. https://github.com/CloverHackyColor/CloverBootloader/releases

自发电遥控开关接收的研究

前面提到了315Mhz和433Mhz 发送接收模块,淘宝上这种发送+接收模块一套只要3.8。

这次文章研究的目标是:如何让接收模块能够收到自发电无线开关(TEL0146)发送的信号。

首先需要对这种模块的基本原理有了解,根据【参考1】资料,它使用调幅AM,OOK方式。我的理解是这种方式下:用无信号表示当前输出为0;发射433.29Mhz的无线信号表示当前输出为1。

此外与这种方式类似还有一种ASK (“振幅键控”)的调制方式:当需要输出0时,发射幅度较低的433.29Mhz的无线信号(下图A1);当输出1时,发射幅度较高的433.29Mhz的无线信号(下图A2)。

在【参考4】给出的频谱分析的结果中可以更清晰的看到二者区别,左侧是 ASK,右侧是 OOK 方式。

当然,我手上没有频谱仪无法“看”到实际发射的信号,但是我可以使用示波器查看接收模块的输出引脚。接收模块有4个引脚,分别是:GND,DATA,DATA,VCC (中间2个引脚都是用于输出的DATA)。经过实验该接收模块刚好能够接收到手中这个发射器的数据,因此就先用它进行实验。

接下来使用示波器直接观察 DATA引脚,了解一下模块输出的形式。使用的代码是RCSwitch 中的例子:

#include <RCSwitch.h>
 
RCSwitch mySwitch = RCSwitch();
 
void setup() {
  Serial.begin(9600);
  mySwitch.enableReceive(2);  // Receiver on interrupt 0 => that is pin #2
}
 
void loop() {
  if (mySwitch.available()) {
     
    Serial.print("Received ");
    Serial.print( mySwitch.getReceivedValue() );
    Serial.print(" / ");
    Serial.print( mySwitch.getReceivedBitlength() );
    Serial.print("bit ");
    Serial.print("Protocol: ");
    Serial.println( mySwitch.getReceivedProtocol() );
 
    mySwitch.resetAvailable();
  }
}

当按下“开”按钮,模块收到 16529064(0x00FF 36A8);当按下“关”按钮,模块收到 16529060(0x00FF 36A4).

使用示波器查看接收模块的输出引脚,可以看到如下波形。就是说当没有数据的时候,引脚为高,之后会拉低一段时间,然后发送数据,发送完毕再次拉高。

类似的,当我使用自发电无线开关时,接收模块也会输出类似波形。因此,它无法工作的问题是软件解码的问题,而不是硬件的问题。

在 RCSwitch.cpp 有下面一段代码:

/* Format for protocol definitions:
 * {pulselength, Sync bit, "0" bit, "1" bit, invertedSignal}
 * 
 * pulselength: pulse length in microseconds, e.g. 350
 * Sync bit: {1, 31} means 1 high pulse and 31 low pulses
 *     (perceived as a 31*pulselength long pulse, total length of sync bit is
 *     32*pulselength microseconds), i.e:
 *      _
 *     | |_______________________________ (don't count the vertical bars)
 * "0" bit: waveform for a data bit of value "0", {1, 3} means 1 high pulse
 *     and 3 low pulses, total length (1+3)*pulselength, i.e:
 *      _
 *     | |___
 * "1" bit: waveform for a data bit of value "1", e.g. {3,1}:
 *      ___
 *     |   |_
 *
 * These are combined to form Tri-State bits when sending or receiving codes.
 */
#if defined(ESP8266) || defined(ESP32)
static const VAR_ISR_ATTR RCSwitch::Protocol proto[] = {
#else
static const RCSwitch::Protocol PROGMEM proto[] = {
#endif
  { 350, {  1, 31 }, {  1,  3 }, {  3,  1 }, false },    // protocol 1
};

意思是使用高电平和低电平不同的比例来确定当前数值为0还是1.比如:高电平持续350us,然后出现一个1050us的低电平,那就是1:3,在 Protocol 1 中,这个值为0;反之高电平持续1050us,然后出现一个350u的低电平,那就是3:1 ,表示值为1。

接下来我们要首先查看一下正确的抓取值,然后再反推编码方式。我们使用 rf433snif-main 这个代码,这个是专用用来统计时间的分析工具。

抓取如下,左侧是原始数据,右侧是我们标记的结果(标注了前面49个数据,因为第50个数据有跟着一个很长的延时,猜测是在数据传输过程中一笔数据会重复三次):

Signal duration: 1445023 us
  N     LOW,  HIGH
-----BEGIN RF433 LOW HIGH SEQUENCE-----
           , 11166
000    1076,   384    1
001    1072,   384    1
002    1072,   392    1
003    1064,   392    1
004    1064,   392    1
005    1064,   396    1
006     328,  1116    0
007     328,  1120    0
008     324,  1120    0
009     320,  1116    0
010    1060,   396    1
011    1064,   400    0
012     324,  1120    0
013    1056,   408    1
014    1052,   408    1
015     316,  1128    0
016    1048,   412    1
017     312,  1128    0
018    1048,   412    1
019     312,  1136    0
020    1044,   416    1
021     308,  1132    0
022     308,  1136    0
023     308,  1136    0
024     308, 11204    0
025    1048,   412    1
026    1048,   416    1
027    1044,   416    1
028    1044,   416    1
029    1044,   420    1
030    1040,   416    1
031     304,  1140    0
032     304,  1140    0
033     304,  1136    0
034     308,  1136    0
035    1044,   420    1
036    1044,   420    1
037     304,  1136    0
038    1044,   416    1
039    1044,   420    1
040     304,  1140    0
041    1040,   424    1
042     300,  1140    0
043    1040,   420    1
044     300,  1140    0
045    1040,   420    1
046     304,  1140    0
047     304,  1140    0
048     304,  1144    0
049     300, 11224
….中间省略…..
-----END RF433 LOW HIGH SEQUENCE-----
Waiting for signal...

看起来这里的猜测需要一些运气,重新写出来是下面这样:

1111110000100110101010000111111000011011010101000
1F84D50FC36A8

很明显最后的 36A8 是我们之前抓到的“开”命令。同样的方法再分析 050-073,也可以得到一个序列:FC36A8。资料中也提到了前面的值可能是厂商定义的型号和地址等信息。

有了上述的知识之后,目标非常明确,设定用于接收的GPIO引脚上会出现一个高低电平的的序列,我们需要根据序列的时间间隔来判断无线开关是否有按下。同样的代码抓取(特别注意,下面的序列是通过给自发电开关上电的方法来让其发射,这样更稳定的。具体抓到的原始数据在后面),整理之后可以看到下面的有规律的排列:

3098,104,296,104,296,104,297,104,296,104,296,104,296,104,296,105,296,104,296,104,296,104,296,305,96,304,96,104,296,104,296,305,96,304,96,304,96,304,96,304,97,303,97,103,297,104,296,104,296,104,
3098,104,296,104,297,104,296,104,296,104,296,104,296,105,296,104,296,104,296,104,296,104,296,304,97,304,96,104,296,104,296,304,97,304,96,304,96,304,96,304,97,303,97,104,296,104,296,104,297,103,
3098,104,296,105,296,104,296,104,296,104,296,104,296,105,296,104,296,104,296,104,296,105,296,304,96,304,96,104,296,105,295,305,96,304,96,304,96,304,97,303,97,303,97,104,296,104,296,104,297,104,
3097,105,295,104,297,104,296,104,296,105,295,104,297,103,297,104,296,104,296,104,296,105,296,304,96,304,96,104,296,105,296,304,96,304,96,304,96,304,97,303,97,304,96,104,296,104,297,104,296,104,

就说说,拉低3100us ,然后是 100us 高,300us低….因此,理论上修改前面的Protocol结构体加入这个特征应该是可以实现的,但是不知道什么原因,无法收到。最终,我编写一个简化版的识别代码:

const unsigned long int SelfSwitch[] =
{
  100, 300, 100, 300, 100, 300, 100, 300, 100, 300,
  100, 300, 100, 300, 100, 300, 100, 300, 100, 300,
  100, 300, 300, 100, 300, 100, 100, 300, 100, 300,
  300, 100, 300, 100, 300, 100, 300, 100, 300, 100,
  300, 100, 100, 300, 100, 300, 100, 300
};
 
static int receiverPin = 2;
static int interruptPin = 0;
unsigned long lastTime = 0;
 
unsigned long int timings;
unsigned int pos;
boolean shoot;
void setup() {
  Serial.begin(1000000);
  pinMode(receiverPin, INPUT_PULLUP);
  interruptPin = digitalPinToInterrupt(receiverPin);
  lastTime = micros();
  attachInterrupt(interruptPin, handleInterrupt, CHANGE);
  Serial.println("Waiting INPUT pin HIGH");
  Serial.println(sizeof(SelfSwitch) / sizeof(unsigned long int));
  while (digitalRead(receiverPin) != HIGH) {}
  pos = 0;
  shoot=false;
}
int MaxP=0;
 
void loop() {
  if (shoot) {
      Serial.println("Fired!");
      shoot=false;
    }
    if (pos>MaxP) {
      Serial.println(pos);
      MaxP=pos;
      }
 
}
 
void handleInterrupt() {
  if (pos < sizeof(SelfSwitch) / sizeof(unsigned long int)) {
    timings = micros() - lastTime;
    lastTime = micros();
  }
 
  // 对于第一个需要特别处理
  if (pos == 0) {
    // 如果第一个在[2900,3100]之间,那么开始记录
    if ((timings > 2900) && (timings < 3100)) {
      pos++;
      return ;
    }
  }
  // 如果超出范围
  if ((timings>SelfSwitch[pos]+80)||(timings<SelfSwitch[pos]-80)) {
      // 超出范围的情况下有可能是新的开始
      if ((timings > 2900) && (timings < 3100)) {
        pos=1;
        return ;
      }
      pos=0;
   } else {
      //没有超过范围,满足要求
      // 如果到达最后一个,表明发生
      if (pos==sizeof(SelfSwitch) / sizeof(unsigned long int)-1) {shoot=true; pos=0;}
      else pos++;
    }
}

当接收模块收到信号时,GPIO会拉低,首先判断这个是否为 3000us的起始标志(特别注意,从2900到3100us都会被认定为启示标志。同样的,后面判定的信号也有一个容错范围)。起始之后,逐一对照SelfSwitch中定义的序列,如果相同,那么继续下一个,否则重新开始。这样,如果都能匹配上就认为确实收到了无线开关的发送信号。

经过测试,代码勉强能够工作,需要离得非常近(5CM),并且需要多按下几次。我猜测是因为发射端功率比较小,并且接收模块灵敏度不是很高的缘故。当然,如果真正想用,需要接收模块更好的天线,更加灵敏的芯片,不妨尝试数字无线开关接收端(TEL0142)。

参考:

  1. https://baike.baidu.com/item/433m%E6%97%A0%E7%BA%BF%E6%A8%A1%E5%9D%97?fromModule=lemma_search-box
  2. http://www.energiazero.org/arduino_sensori/Complete%20Guide%20for%20RF%20433MHz%20Transmitter.pdf
  3. https://www.rfwireless-world.com/Terminology/OOK-vs-FSK-vs-ASK.html

What is ASK and OOK Modulation

1.  抓取的能够正常通讯的遥控器数值:
Signal duration: 1445023 us
  N     LOW,  HIGH
-----BEGIN RF433 LOW HIGH SEQUENCE-----
           , 11166
000    1076,   384   
001    1072,   384
002    1072,   392
003    1064,   392
004    1064,   392
005    1064,   396
006     328,  1116
007     328,  1120
008     324,  1120
009     320,  1116
010    1060,   396
011    1064,   400
012     324,  1120
013    1056,   408
014    1052,   408
015     316,  1128
016    1048,   412
017     312,  1128
018    1048,   412
019     312,  1136
020    1044,   416
021     308,  1132
022     308,  1136
023     308,  1136
024     308, 11204
025    1048,   412
026    1048,   416
027    1044,   416
028    1044,   416
029    1044,   420
030    1040,   416
031     304,  1140
032     304,  1140
033     304,  1136
034     308,  1136
035    1044,   420
036    1044,   420
037     304,  1136
038    1044,   416
039    1044,   420
040     304,  1140
041    1040,   424
042     300,  1140
043    1040,   420
044     300,  1140
045    1040,   420
046     304,  1140
047     304,  1140
048     304,  1144
049     300, 11224
050    1036,   420
051    1040,   420
052    1040,   420
053    1044,   416
054    1044,   424
055    1036,   424
056     300,  1144
057     304,  1140
058     304,  1140
059     304,  1140
060    1040,   424
061    1036,   424
062     300,  1144
063    1036,   424
064    1040,   424
065     300,  1144
066    1036,   424
067     304,  1140
068    1040,   428
069     296,  1148
070    1036,   424
071     300,  1144
072     304,  1148
073     300,  1144
074     300, 11232
075    1036,   420
076    1040,   424
077    1036,   424
078    1040,   424
079    1036,   424
080    1036,   424
081     300,  1144
082     300,  1144
083     300,  1144
084     300,  1144
085    1036,   424
086    1036,   424
087     300,  1148
088    1032,   428
089    1040,   420
090     300,  1148
091    1036,   424
092     300,  1144
093    1040,   424
094     300,  1148
095    1032,   428
096     296,  1148
097     300,  1148
098     300,  1148
099     300, 11232
100    1036,   424
101    1040,   420
102    1040,   424
103    1036,   424
104    1036,   424
105    1036,   428
106     296,  1148
107     300,  1144
108     300,  1144
109     300,  1148
110    1036,   424
111    1036,   424
112     300,  1144
113    1036,   424
114    1036,   432
115     296,  1152
116    1032,   424
117     300,  1148
118    1036,   428
119     300,  1144
120    1040,   428
121     296,  1148
122     296,  1144
123     300,  1148
124     296, 11232
125     508,175872
126     200,   880
127     676,   160
128     848,   124
129  200000,     0
130  200000,     0
131  200000,     0
132  200000,     0
133   12296, 11176
134    1072,   392
135    1064,   396
136    1064,   396
137    1064,   400
138    1060,   404
139    1056,   404
140     316,  1132
141     312,  1128
142     312,  1128
143     316,  1128
144    1052,   412
145    1048,   412
146     312,  1128
147    1052,   408
148    1048,   412
149     312,  1132
150    1048,   412
151     312,  1132
152    1048,   412
153     312,  1132
154    1048,   412
155     312,  1132
156     312,  1136
157     308,  1136
158     308, 11212
159    1048,   412
160    1048,   412
161    1048,   416
162    1044,   416
163    1044,   412
164    1048,   416
165     308,  1136
166     308,  1136
167     308,  1136
168     308,  1136
169    1048,   416
170    1044,   416
171     308,  1132
172    1048,   416
173    1044,   416
174     308,  1136
175    1044,   416
176     308,  1140
177    1040,   416
178     308,  1140
179    1044,   416
180     308,  1140
181     308,  1140
182     304,  1140
183     304, 11224
184    1044,   416
185    1044,   416
186    1044,   416
187    1044,   416
188    1044,   416
189    1048,   420
190     304,  1144
191     304,  1136
192     308,  1140
193     304,  1140
194    1044,   416
195    1044,   420
196     308,  1140
197    1044,   420
198    1044,   420
199     304,  1140
200    1044,   420
201     304,  1140
202    1044,   420
203     304,  1140
204    1044,   420
205     304,  1140
206     308,  1140
207     304,  1140
208     308, 11228
209    1044,   420
210    1040,   420
211    1040,   420
212    1040,   420
213    1044,   416
214    1044,   416
215     308,  1144
216     304,  1140
217     308,  1144
218     300,  1140
219    1044,   416
220    1044,   420
221     308,  1136
222    1040,   420
223    1040,   424
224     300,  1144
225    1040,   424
226     300,  1144
227    1040,   420
228     304,  1144
229    1040,   420
230     304,  1140
231     304,  1140
232     304,  1140
233     304, 11236
234    1040,   416
235    1044,   420
236    1044,   420
237    1044,   420
238    1044,   420
239    1044,   420
240     304,  1140
241     304,  1148
242     300,  1140
243     308,  1136
244    1044,   420
245    1040,   420
246     304,  1140
247    1040,   420
248    1040,   424
249     304,  1140
-----END RF433 LOW HIGH SEQUENCE-----
Waiting for signal...
2.抓取的自发电开关数据。
⸮⸮)⸮Signal duration: 210193 us
  N     LOW,  HIGH
-----BEGIN RF433 LOW HIGH SEQUENCE-----
           , 38599
000     116,  3084
001     112,   284
002     108,   288
003     108,   284
004     112,   284
005     108,   284
006     112,   284
007     112,   284
008     112,   284
009     112,   284
010     108,   284
011     108,   288
012     308,    88
013     308,    88
014     108,   288
015     108,   288
016     308,    92
017     304,    92
018     304,    92
019     304,    92
020     304,    92
021     304,    92
022     104,   292
023     104,   292
024     104,   292
025     104,  3092
026     104,   288
027     104,   292
028     104,   292
029     104,   292
030     104,   292
031     104,   292
032     104,   292
033     104,   292
034     104,   292
035     104,   292
036     104,   292
037     304,    92
038     304,    92
039     104,   292
040     104,   292
041     304,    92
042     304,    92
043     304,    92
044     108, 98916
045      36,  1072
046      40,  2668
047      68,   740
048      64,  3240
049     148,    28
050      36,  2304
051      40,   392
052      32,   600
053      60,    48
054     216,   856
055      20,   588
056     128,   284
057      20,   876
058      48,   392
059      36,  1696
060      72,   988
061      60,   360
062      84,   104
063      24,  1220
064     128,   140
065      64,   216
066      52,  1108
067      52,    96
068      32,   100
069      16,   176
070      44,   760
071      88,   192
072      60,   648
073     100,   720
074     196,   752
075     100,  1136
076      96,    68
077      32,   288
078      52,   284
079      20,   176
080     136,   372
081      24,    60
082      60,   140
083      56,    76
084     352,   492
085      36,   320
086      76,   244
087     100,   144
088     364,   720
089      68,   132
090     204,   336
091     236,    32
092     100,   100
093     232,   380
094     192,    28
095     264,   132
096      28,    28
097     136,   752
098      60,   312
099      88,   360
100     236,   588
101     104,   432
102      92,   156
103      56,    40
104     176,   252
105      72,   456
106      36,    92
107     160,    88
108     112,   336
109     232,    56
110     264,    80
111      56,   160
112     136,    44
113     272,    88
114     136,    40
115     108,   112
116     372,    84
117      56,   532
118     284,    48
119     360,    88
120      36,   164
121      80,    88
122      88,   212
123      64,    12
124      32,   116
125     124,   192
126     376,   260
127     112,   296
128      56,    84
129     128,   328
130     128,   592
131     200,   608
132     380,   172
133      40,   140
134     100,   148
135      56,   480
136      64,   356
137     640,   356
138     120,    12
139      16,   220
140     124,   536
141     320,    84
142      60,    80
143      44,   128
144     120,    52
145      72,    44
146     108,    36
147     204,    76
148      88,   224
149     308,   108
150       8,    52
151      64,     8
152     128,    36
153      80,   428
154     204,    96
155     108,    68
156      88,   180
157     116,    28
158     172,   136
159      76,    40
160      92,    84
161      52,   516
162     472,   472
163     156,    84
164     292,   368
165     180,   120
166     268,   196
167     248,     0
168     188,   236
169     172,   104
170      24,    36
171     140,   180
172     180,   192
173     108,   136
174     112,   140
175      72,    16
176     292,    48
177     164,   136
178       8,   240
179      76,    92
180     156,    32
181     264,   292
182     600,   260
183      88,    76
184     412,    88
185     108,   184
186      60,    52
187     236,     0
188     168,    68
189      76,    80
190     100,   568
191     436,   156
192     108,    72
193      68,   304
194     120,   544
195       4,    20
196     180,    68
197      48,    48
198     188,   244
199     160,   104
200     496,   184
201     148,   276
202       8,   116
203      64,    24
204     168,   520
205      20,   172
206     104,   340
207     108,    72
208     384,    48
209     164,   164
210     176,    76
211     228,    40
212     200,   248
213     188,   132
214     328,    92
215     152,   112
216     176,    64
217     132,    80
218      76,   144
219     196,   228
220     140,    40
221      16,   280
222     272,   100
223     900,   152
224      48,    92
225     572,    52
226      68,   196
227     120,   256
228     156,   128
229      48,    48
230      36,   128
231     772,   232
232     516,    60
233     272,   172
234     164,    92
235     568,   144
236      80,   120
237      16,     8
238     164,    28
239      16,    88
240     304,   180
241     652,   340
242      48,   108
243     116,    32
244     264,    36
245     200,   192
246     448,    68
247     180,   552
248      80,    80
249      80,    76
-----END RF433 LOW HIGH SEQUENCE-----
Waiting for signal...

自发电遥控器遥控台灯

感谢 DFRobot 提供的自发电无线开关试用(TEL0146)。这个开关和其他模块最大的差别在于:它本身无需外部供电即可工作。基本原理是:在按下开关的时候,按键会推动磁铁在线圈中发电,虽然能量很小但是足以让CMT2156B这个OOK编码发射芯片进行工作(这个模块使用了CMT2156B芯片,它 是一款针对微能量收集并进行发射的单芯片,内嵌可配置编码的高性能OOK 射频发射器,支持240 至960 MHz 的能量收集无线发射的应用。该芯片集成的编码器兼容市面上最常用的527、1527、2262 和2240 等编码格式,还支持用户各种自定义编码)。这样就能够设计出完全不需要外部供电和电池的无线设备。

DFRobot快递包裹包装中还有一个接收模块(TEL0142)和一把PCB 格尺:

使用这个自发电无线开关的第一个作品是遥控台灯。除了无线开关,还用到了下面这个数字无线开关接收端(TEL0142)。

这个接收模块带有三个引脚,分别是 VCC/GND、SIGNAL。正常配对之后SIGNAL 会跟随自发电无线开关按键发生变化。收到数据后,SIGNAL Pin 会从低变高,之后会再次变低。这个模式称为“点动模式”。此外还有三个不同的模式,具体可以在 WIKI 上看到【参考1】。接收模块使用 IO2对ESP32输入。

模式说明
锁存D0收到一次信号后就一直保持高电平,直到D1~D3收到信号
自锁D0每收到一次信号,对应的输出状态就反转一次,D1~D3同理
点动D0收到信号输出高电平,没有收到信号低电平,D1~D3同理
互锁D0收到信号,D0就保持高电平,其余全部低电平,D1~D3同理

实验试用 ESP32 FireBeetle 作为主控端。此外,还使用了一个淘宝上购买的433Mhz模块作为发送端,这个发送模块在另外一篇文章有所介绍【参考2】,模块使用 IO17作为发送数据Pin。

代码非常简单,当发现 IO2为低,并且距离上一次拉低超过2秒,就发送控制433Mhz无线开关的命令,从而达到自发电无线开关遥控的目的。

1234567891011121314151617181920212223242526272829303132#include<RCSwitch.h>RCSwitch mySend =RCSwitch();voidsetup() {  pinMode(2,INPUT_PULLUP);  Serial.begin(115200);  mySend.enableTransmit(17);    // Optional set protocol (default is 1, will work for most outlets)   mySend.setProtocol(1);  }longintElsp=0;booleanOnFlag=false;voidloop() {  if((millis()-Elsp>2000)&&(digitalRead(2)==HIGH)) {      Serial.print("Fire");      if(OnFlag) {          // 开          mySend.send(16529064, 24);          Serial.println("Send on command");        }      else{          // 关          mySend.send(16529060, 24);          Serial.println("Send off command");              OnFlag=!OnFlag;      Elsp=millis();    }}

参考:

  1. https://wiki.dfrobot.com.cn/_SKU_TEL0140orTEL0142_Gravity_%E6%95%B0%E5%AD%97%E9%87%8F%E6%97%A0%E7%BA%BF%E5%BC%80%E5%85%B3
  2. https://mc.dfrobot.com.cn/thread-313862-1-1.html

SST25VF080B 测试

SST25VF080B 是一款 SPI NOR 芯片,容量为1MB(8MBITs)【参考1】。这次测试的使用 Arduino Pro Micro 【参考2】进行读写(它使用32U4 作为主控,工作在8Mhz,3.3V 下面)。特别注意,这个SPI NOR 工作电压为3.3V。

具体连接如下:

编写的测试代码如下:

#include &lt;SST25VF.h>
#include &lt;SPI.h>
 
 
#define MemCs 6 //CS#
#define MemWp 4 //WP#
#define MemHold 7 //HOLD#
#define EraseSwitch  5 //擦除开关
#define WriteSwitch  8 //写入开关
 
SST25VF flash;
 
uint8_t buffer[]="www.lab-z.com 2022"; 
 
void setup() {
  Serial.begin(115200);
  pinMode(EraseSwitch, INPUT_PULLUP);
  pinMode(WriteSwitch, INPUT_PULLUP);
   
  flash.begin(MemCs, MemWp, MemHold);
}
 
void loop() {
  // 函数内部直接串口输出
  flash.readID();
  if (digitalRead(EraseSwitch) == LOW) {
    // 擦除 Sector 0
    Serial.println("erasing...");
    flash.sectorErase(0);
  }
  if (digitalRead(WriteSwitch) == LOW) {
    // 写入字符串
    Serial.println("writing...");
    flash.writeArray(0,buffer,sizeof(buffer));
  }
 
  //读取
  flash.readArray(0,buffer,sizeof(buffer));
  Serial.print("Read: ");
  for (int i=0;i&lt;sizeof(buffer);i++) {
      Serial.print((char)buffer[i]);
    }
  Serial.println("");
 
  delay(5000);
}

代码会读取当前芯片的 ID显示出来,然后读取 Address 0开始的数据,以 ASCII字符显示出来。同时预留了D5和D8。当D5接地时会进行Sector 0 的擦除动作;当D8接地时会对 Address 0 写入一个字符串。

SST25VF080B-master下载

参考:

1.芯片收据手册 https://ww1.microchip.com/downloads/en/DeviceDoc/20005045C.pdf

2.Arduino Pro Micro 引脚定义 https://content.arduino.cc/assets/Pinout-Micro_latest.pdf

CH567 自动烧写工具

最近我在使用 CH567 制作双 USB 设备,在这个过程中,我发现每次烧写程序都比较麻烦。例如,首先要拔掉设备,然后按下 DOWNLOAD 按键,接下来再插入USB端口中,最后才能烧写(值得庆幸的是我预留了RESET 按钮,否则还要重新插拔一次)。经过研究和实验,我设计了一个能够自动完成烧写的设备。基本原理是:使用芯片模拟USB设备下电过程(断开 D+、D-,再下电);然后通过MOSFET模拟按下DOWNLOAD键(Pin 下拉到GND);再使用芯片模拟USB设备上电过程(先上电,再连接 D+、D-);接下来用户就可以在PC上进行烧写;最后再通过MOSFET模拟RESET 键。烧写好的程序就可以正常运行了。

下面介绍这次用到的两颗主要芯片。首先是用于切换USB 信号(D+、D-)的CH442。它是 DPDT 模拟开关芯片,包含 2 路单刀双掷二选一开关。CH442 是CH44X 系列芯片的一款。这一系列是模拟开关芯片,具有高带宽,支持视频信号,支持低速、全速和高速 USB 信号的特点。

另外一个是 SY6280AAC芯片,用于控制 USB母头(连接CH567)的 USB 供电。

这次使用的主控为 DFRbot 出品的 FireBeetle,他的核心是 ESP32。实际上主要功能是通过GPIO来实现的,要求不高,有需要的朋友可以修改为任意的单片机。

最终电路图设计如下:

主控部分需要注意的是预留了一个跳线位置,必要时将此短接起来FireBeetle可以直接从USB1取电(例如,FireBeetle支持蓝牙,可以修改代码为蓝牙控制触发 CH567进去下载模式)。

下面是2个GPIO 通过MOSFET控制的RST和 PowerDown 引脚。没有触发时,RST和PWDPIN会出现3.3V,当有需要时可以拉低到GND。

接下来是USB接口和用于切换的CH442E 芯片,USB1 是USB公头,接入电脑中;USB3 是USB母头,用于连接CH567。CH442E控制USB1 的 USB信号(IN_D-、IN+D+)在USB3(DB、DC)和断开(S2B、S2C)之间切换。

最后是SY6280AA芯片,UPW_CTRL 控制IN_VCC 是否输出到 OUT_VCC 上。

这个设计是给FireBeetle 的Shield,最终的PCB 如下:

焊接之后照片如下,右侧是 DFRobot的FireBeetle,二者可以通过堆叠的方式进行连接。

完整代码如下:

#define PWD_CTRL 22
#define EN_CTRL  27
#define RST_CTRL 21
#define UPW_CTRL 14
#define IN_CTRL 26
 
void setup() {
  Serial.begin(115200);
  pinMode(PWD_CTRL, OUTPUT);
  pinMode(EN_CTRL, OUTPUT);
  pinMode(RST_CTRL, OUTPUT);
  pinMode(UPW_CTRL, OUTPUT);
  pinMode(IN_CTRL, OUTPUT);
 
  digitalWrite(PWD_CTRL, LOW);
  digitalWrite(EN_CTRL, LOW);
  digitalWrite(RST_CTRL, LOW);
  digitalWrite(UPW_CTRL, LOW);
  digitalWrite(IN_CTRL, LOW);
 
}
 
void unplug() {
  // USB1和USB3断开
  digitalWrite(IN_CTRL, LOW);
  Serial.print(IN_CTRL);
  Serial.println("IN_CTRL  LOW");
 
  // CH567 断开5V
  digitalWrite(UPW_CTRL, LOW);
  Serial.print(UPW_CTRL);
  Serial.println("UPW_CTRL  LOW");
  delay(20);
}
 
void plug() {
  // CH567 上电
  digitalWrite(UPW_CTRL, HIGH);
  Serial.print(UPW_CTRL);
  Serial.println("UPW_CTRL  HIGH");
  delay(20);
 
  // USB1和USB3连通
  digitalWrite(IN_CTRL, LOW);
  Serial.print(IN_CTRL);
  Serial.println("IN_CTRL  HIGH");
}
 
void loop() {
  if (Serial.available()) {
    byte c = Serial.read();
    // 按下 PowerDown
    if (c == 'd') {
      Serial.println("Press Power Down");
      // 按下 PowerDown 键
      digitalWrite(PWD_CTRL, HIGH);
      Serial.println("PWD_CTRL  HIGH");
    }
    // 抬起 PowerDown
    if (c == 'u') {
      Serial.println("Release Power Down");
      //抬起 PowerDown 键
      digitalWrite(PWD_CTRL, LOW);
      Serial.println("PWD_CTRL  HIGH");
    }
 
    // 按下抬起一次 RESET键
    if (c == 'r') {
      Serial.println("Press and Release RESET");
      //按下 RESET 键
      digitalWrite(RST_CTRL, HIGH);
      Serial.println("RST_CTRL  HIGH");
      delay(20);
      //抬起 PowerDown 键
      digitalWrite(RST_CTRL, LOW);
      Serial.println("RST_CTRL  LOW");
    }    
    // 模拟断开CH567 
    if (c == 'u') {
      Serial.println("unplug");
      unplug();
    }
    // 模拟插入CH567 
    if (c == 'p') {
      Serial.println("plug");
      plug();
    }    
  }
}

使用方法是通过串口接收指令,代码定义了如下五个动作:

1.”d” 按下 PowerDown键

2.”f” 抬起 PowerDown键

3.”r” 按下然后抬起 RESET键

4.”u” 模拟断开 USB 母头设备

5.”p” 模拟插入 USB 母头设备