ESP32 I2C Slave Mode

ESP32 目前支持 I2C 的 Slave Mode ,就是说可以作为一个 I2C 设备存在。

具体的介绍在下面能看到,是一个大佬写了一个 Slave Mode 的库,后来整合到了官方 Release 中。

https://github.com/espressif/arduino-esp32/pull/5746/commits/f9f70d2f73d16f7fb50f59e05323cd041acce830

安装完最新的ESP32 Arduino支持包之后,可以在下面的路径中看到:

C:\Users\UserName\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.6\libraries\Wire

其中的 WireSlave 就是实现一个 I2C Slave 的完整例子。

充放电、升压、一键开机和断电验测试板

这是一个电路测试板,能够实现下面的功能:

  1. 锂电池充放电管理,5V输出
  2. 按键开机,MCU 控制关机(自己给自己切断电源)
  3. 5V升压

整体电路图如下,可以看到分成四部分:锂电池充放电(第一部分),一键开机和MCU关机(第二部分)、5V升压(第三部分)和锂电池座(第四部分):

首先介绍第一部分,核心是 IP5306模块,接口部分定义如下:

引脚功能介绍
1USBINPin1是 USBIN ,连接 MCU ,设置为 INPUT_PULLUP,当USB充电时,会被拉低;当没有充电时会设置为高。从而MCU通过读取这个GPIO 能够得知当前是否有正在进行充电。IP5306 没有反映当前充电状态的引脚,所使用这个设计来获得充电状态;
2OUT1Pin2 是5V输出。当没有对外供电时,这里有4V 左右的电压输出;当外部插入取电时,或者SW2按钮按下时,这里会有5V输出;
3GND 
4BATIN连接电池正极输入;
5GND 
6BAT_ADC一个分压输出,MCU 的 ADC 能够获得当前的电池电压信息

此外这部分的 SW2是一个按钮,短按可以让 IP5306输出5V,再次按下会切断输出,如果负载<50ma,那么 45s之后也会停止输出

接下来是第二部分:

这部分根据【参考1】而来,很好用。接口定义如下:

引脚功能介绍
1IN2输入(第一部分输出的OUT1 可以接入这里)
2OUT2控制后的输出
3IN2同上 IN2
4OUT2同上 OUT2
5CTRL输出控制脚,初始时MCU 需要通过 CTRL对这里输入一个高电平,当需要断电时CTRL输入低电平随即切断Pin2的输出
6GND 
   

这部分也带有一个按键,按下之后 Pin2 即可输出(需要按的稍微长一些,保证MCU 的 CTRL能够输出高电平)

第三部分,基于MT3608 芯片的5V升压设计,具体芯片 DataSheet可以在【参考2】看到,这个也是也是来自开源广场别人的设计(不过忘记是哪篇了,找了一下没找到),接口定义如下:

引脚功能介绍
1IN3电源输入,例如输入3.3V
2OUT3电源输出,5V
3IN3同上IN3
4OUT3同上OUT3
5GND 
6GND 

简单功耗测量,测试方法是在电池串联万用表测量电流。5V对ESP32 S3 板【参考3】输出时,电流在90ma左右;MCU 切断供电后,电流在5ma左右;经过45s后IP5306自动断电后电流在0.04ma左右。

成品

 工作视频在:

上述主要芯片除了电容电阻,其余都是购买自立创商城,有兴趣的朋友可以实验。

参考:

  1. https://oshwhub.com/armxu/kai-ji-zi-dong-guan-ji-dian-lu
  2. https://atta.szlcsc.com/upload/public/pdf/source/20161110/1478743351706.pdf
  3. https://mc.dfrobot.com.cn/thread-315546-1-1.html

SPI NOR 芯片测试板

为了测试 SPI NOR, 做了一个测试的小板,一面可以使用 SOP8 的SPI NOR, 一面可以使用 SOP16 的SPI NOR。对于目前我们使用的 SOP8 的 SPI NOR 来说,有8个引脚:

其中的 WP# 是写保护,低有效(拉低的时候无法写入);HOLD# 是暂停操作,比如,当前正在写入,HOLD# 拉低之后暂停写入,拉高后继续写入而无需重新发送写入命令。

SPINOR 测试板PCB
SPINOR 测试板成品

电路图和PCB下载:

STB下载

Step to UEFI (275)UEFI 创建内存虚拟盘

前面介绍过在 UEFI 下创建内存虚拟盘的操作,这次介绍如何创建包含需要内容的内存虚拟盘。生成的虚拟盘可以在没有硬盘的情况下充当临时文件的存放位置。当然如果关机或者断电后,盘中内容会消失。

第一步,根据【参考1】制作一个磁盘镜像VHD,这里出于体积考虑,制作了一个 64MB 的FAT32 空盘;

第二步,上面制作磁盘镜像大部分内容都是 0x00(可以看作是以512Bytes为单位的稀疏矩阵),因此我们还需要一个工具提取文件中的非零内容保存起来,这样能够有效降低镜像尺寸。使用 C# 编写代码如下。简单的说就是以512Bytes为单位读取文件,然后检查512 bytes 中是否为全0.如果不是就记录下来保存到文件中。每一个项目是由 4 Bytes 记录加一个 512Byte的数组构成。特别注意 VHD 镜像文件末尾包含了一个文件头,我们这里会对文件大小取整,对于末尾的文件头不会进行处理。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace ConsoleApp7
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Count() == 0) {
                Console.WriteLine("Please input file name!");
                Console.ReadKey();
                Environment.Exit(0);
            }

            if (File.Exists((args[0] + ".raw"))) {
                File.Delete(args[0] + ".raw");
            }
            FileStream fs = new FileStream(args[0], FileMode.Open);
            FileStream RawFs = new FileStream(args[0]+".raw", FileMode.CreateNew);
            byte[] array = new byte[512];
            UInt32 Data;
            Boolean EmptyMark;

            // 写入 Disk Image 的Size (以 1MB 为单位)
            Data = (UInt32) fs.Length / 1024 / 1024;
            RawFs.Write(BitConverter.GetBytes(Data), 0, 4);
            
            // 处理整数以内的磁盘,例如:扫描 64MB 以内的内容生成及镜像
            while (fs.Position < fs.Length / 1024 / 1024 * 1024 * 1024) {
                EmptyMark = true;
                fs.Read(array, 0, array.Length);
                for (int i = 0; i < array.Length; i++)
                {
                    if (array[i] != 0x00)
                    {
                        EmptyMark = false;
                        break;
                    }
                }
                if (EmptyMark==false)
                {
                    Data = (UInt32)(fs.Position / 512 - 1);
                    RawFs.Write(BitConverter.GetBytes(Data),0,4);
                    RawFs.Write(array, 0, array.Length);
                    Console.WriteLine("{0}", Data);
                }
            }
            RawFs.Close();
            Console.WriteLine("Done!");

        }
    }
}

例如:64MB的FAT32 空盘,经过上述操作后会变成6K 大小。

第三步,编写Shell 下的 UEFI Application,创建内存虚拟盘,然后读取前述生成的文件,将内容放在虚拟盘中。这样就得到了一个带有文件系统的内存虚拟盘。代码如下:

#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/RamDisk.h>
#include <Protocol/DevicePathToText.h>
#include <Protocol/HiiDatabase.h>
#include <Protocol/HiiPackageList.h>
#include <Protocol/HiiImageEx.h>
#include <Protocol/PlatformLogo.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/ShellLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>

//DO NOT REMOVE IMAGE_TOKEN (IMG_LOGO)

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
EFIAPI
UefiMain (
    IN EFI_HANDLE        ImageHandle,
    IN EFI_SYSTEM_TABLE  *SystemTable
)
{
	EFI_STATUS               Status;
	EFI_RAM_DISK_PROTOCOL    *MyRamDisk;
	EFI_FILE_HANDLE   		FileHandle;
	UINTN	tmp, DiskSize,SectorIndex;
	UINT64                   *StartingAddr,Position;
	UINT8                    Sector[512];
	UINTN					 ImageFileSize;
	EFI_DEVICE_PATH_PROTOCOL *DevicePath;

	// 磁盘镜像文件
	CHAR16	*DiskImage=L"DiskImage.BIN";
	// 如果磁盘镜像不存在,报错退出
	if (ShellFileExists(DiskImage)!=EFI_SUCCESS)
	{
		Print(L"Couldn't find 'DiskImage.bin'\n");
		return EFI_INVALID_PARAMETER;
	}

	// 打开磁盘镜像文件
	Status = ShellOpenFileByName(
	             DiskImage,
	             (SHELL_FILE_HANDLE *)&FileHandle,
	             EFI_FILE_MODE_READ,
	             0);
	if(Status != RETURN_SUCCESS)
	{
		Print(L"OpenFile failed!\n");
		return EFI_INVALID_PARAMETER;
	}


	// Look for Ram Disk Protocol
	Status = gBS->LocateProtocol (
	             &gEfiRamDiskProtocolGuid,
	             NULL,
	             &MyRamDisk
	         );
	if (EFI_ERROR (Status))
	{
		Print(L"Couldn't find RamDiskProtocol\n");
		return EFI_ALREADY_STARTED;
	}

	tmp=4;
	Status = ShellReadFile(FileHandle,&tmp,&DiskSize);
	Print(L"Disk size %dMB\n",DiskSize);
	DiskSize=DiskSize*1024*1024;

	//Allocate a memory for Image
	StartingAddr = AllocateReservedZeroPool	((UINTN)DiskSize);	 
	if(StartingAddr==0)
	{
		Print(L"Allocate Memory failed!\n");
		return EFI_SUCCESS;
	}

	ShellGetFileSize(FileHandle,&ImageFileSize);
	Position=0;
	Print(L"File size %d\n",ImageFileSize);

	while (Position<ImageFileSize)
	{
		tmp=4;
		Status = ShellReadFile(FileHandle,&tmp,&SectorIndex);
		if (Status!=EFI_SUCCESS)
		{
			break;
		}
		Print(L"Sector index %d\n",SectorIndex);
		tmp=512;
		Status = ShellReadFile(FileHandle,&tmp,&Sector);
		if (Status==EFI_SUCCESS)
		{
			//Print(L"Read success %d\n",(UINT8*)StartingAddr+SectorIndex*512);
			CopyMem((UINT8*)StartingAddr+SectorIndex*512,&Sector,tmp);
		}
		ShellGetFilePosition(FileHandle,&Position);
		//Print(L"postion %d\n",Position);
	}

	//
	// Register the newly created RAM disk.
	//
	Status = MyRamDisk->Register (
	             ((UINT64)(UINTN) StartingAddr),
	             DiskSize,
	             &gEfiVirtualDiskGuid,
	             NULL,
	             &DevicePath
	         );
	if (EFI_ERROR (Status))
	{
		Print(L"Can't create RAM Disk!\n");
		return EFI_SUCCESS;
	}
	ShellCloseFile(&FileHandle);

	return EFI_SUCCESS;
}

基本思路就是:打开镜像文件读取4 Bytes,然后创建这个大小的 Memory Disk。接下来读取 4 Bytes的扇区位置,然后再读取512字节的扇区内容,将这个内容存放在内存中的对应位置。

在Shell 下执行这个程序后,使用 map -r 即可看到新生成的内存盘。

UEFI 代码和编译后的EFI 文件,同时内置了一个64MB的磁盘镜像。

前面提到的 Windows 下的磁盘镜像扫描工具。内置了一个 64MB的空磁盘镜像。

参考:

1. https://www.lab-z.com/vt/