Skip to content

Commit

Permalink
First (untested) implementaiton of direct SPI flash read & write - e.…
Browse files Browse the repository at this point in the history
…g. for RPI
  • Loading branch information
btsimonh committed Jan 25, 2022
1 parent ac2619a commit c600685
Show file tree
Hide file tree
Showing 6 changed files with 881 additions and 0 deletions.
96 changes: 96 additions & 0 deletions SPIFlash.md
@@ -0,0 +1,96 @@
# BK7231S Flash via SPI

This is useful if you accidentally overwrite the bootloader.

This procedure was tested on an RPI3b having enabled SPI in raspi-config.

Connections:

```
CEN - Chip enable (reset) -> GPIO22
P20 - SCLK -> SCLK
P21 - FLASH CSN -> SPI CE0
P22 - FLASH SI -> SPI MOSI
P23 - FLASH SO -> SPI MISO
SPI Mode 3
SPI Rate 50000
```

## Enabling SPI Flash

set GPIO22 (CEN) to low.

Wait 1s.

set GPIO22 (CEN) to high.

Send 250 'D2'

Expected response:

First byte D2, 249 x 00


The MCU is now in SPI flashing mode, and will remain in this state (until power off, or maybe toggle of CEN?).

Test by sending

`9F 00 00 00`
->
`00 15 70 1C`

This is the ID of an EN25QH16B - data sheet here: https://datasheetspdf.com/datasheet/EN25QH16B.html


## Reading the flash

data may be read in chunks of 256 bytes using cmd 03:

`03 (addr>>16 & 0xFF) (addr>>8 & 0xFF) (addr & 0xFF) <256 x 00>`

The data read in this way is from the raw flash. Executable partitions on the BK7231 may/will be both packaged and encrypted.


## writing the flash

Writing consists of erasing sectors and then writing data.

As it is writing the faw flash, executable partitions must be encrypted and packaged (32->34 bytes with CRC).

From the Tuya SDK, the firmware file tagged '_QIO_' is suitable. e.g. to replace the bootloader, flash the first 0xf000 bytes from that firmware file.

Each command must raise CSN at the end to take effect, so all commands must be sent separately over SPI.

A sector is 0x1000 bytes, writing is max 256 bytes per page.

Procedure:

Enable write (must be done before each erase or write):

`06`

Erase sector:

`20 (addr>>16 & 0xFF) (addr>>8 & 0xFF) (addr & 0xFF)`

Wait for Erase to complete:

`05 00`

Repeat until bit 0 of the second byte returns clear.

Enable write (must be done before each erase or write):

`06`

Write data:

`03 (addr>>16 & 0xFF) (addr>>8 & 0xFF) (addr & 0xFF) <up to 256 bytes of data>`

Note that there are 4 writes to each erase. If you call erase with a write addr not on a 0x1000 boundary, it will erase the sector starting on the relevant 0x1000 boundary.



## Additional notes:

At first, I attempted this from Espruino on ESP32. That was not successful, and I'm still not sure why.
41 changes: 41 additions & 0 deletions bkutils/chip/BK3435DownloadFormatSPI.py
@@ -0,0 +1,41 @@
import time
from .ExternDownloadFormatSPI import CHIP_EXTERN_Reset, CHIP_EXTERN_End
from ..hid_commands import RESET_ENABLE_PIN, WRITE_COMMAND_CMD, SOFT_SPI, HARD_SPI, DOWNLOAD_STATE, VPP_VCC_POWER_UPDATE

FT_MSG_SIZE_FLASH = 0x40

def CHIP_BK3435_Reset(spi_downloader, DownFormat):
spi_downloader.ChipReset()
for _ in range(150):
send_buf[0] = 0xD2
reply = spi_downloader.spi.xfer2(send_buf)

time.sleep(0.1)

return CHIP_EXTERN_Reset(hid_downloader)


def CHIP_BK3435_Start(*arg):
return True

def CHIP_BK3435_End(hid_downloader):
return CHIP_EXTERN_End(hid_downloader)

def ChangeInterface(hidDev, DownFormat):
send_buf = bytearray(5)
send_buf[0] = VPP_VCC_POWER_UPDATE
send_buf[1] = DOWNLOAD_STATE
send_buf[2] = HARD_SPI
send_buf[3] = 100
send_buf[4] = DownFormat
hidDev.WriteHid(send_buf)

out_buf = hidDev.ReadHid(1)
if out_buf[0] != 0xEE:
print("Vpp打开失败!")
return False

return True



138 changes: 138 additions & 0 deletions bkutils/chip/ExternDownloadFormatSPI.py
@@ -0,0 +1,138 @@
import time
import struct
from ..hid_commands import *

FT_MSG_SIZE_FLASH = 0x40

SPI_CHIP_ERASE_CMD = 0xc7
SPI_CHIP_ENABLE_CMD = 0x06
SPI_READ_PAGE_CMD = 0x03
SPI_WRITE_PAGE_CMD = 0x02
SPI_SECTRO_ERASE_CMD = 0x20
SPI_SECUR_SECTOR_ERASE = 0x44
SPI_ID_READ_CMD = 0x9F
SPI_STATU_WR_LOW_CMD = 0x01
SPI_STATU_WR_HIG_CMD = 0x31
SPI_READ_REG = 0x05

def Wait_Busy_Down(spi):
while True:
send_buf = bytearray(2)
send_buf[0] = SPI_READ_REG
send_buf[1] = 0x00
out_buf = spi.xfer2(send_buf)
if not (out_buf[0] & 0x01):
break
time.sleep(0.1)

def Read_ID(spi):
send_buf = bytearray(4)
send_buf[0] = SPI_ID_READ_CMD
send_buf[1] = 0x00
send_buf[2] = 0x00
send_buf[3] = 0x00
out_buf = spi.xfer2(send_buf)
flash_id = out_buf[1:4]
flash_id.append(0)
flash_id = bytes(flash_id)
flash_id = struct.unpack('<I', flash_id[0:4])[0]
return flash_id

def CHIP_ENABLE_Command(spi):
send_buf = bytearray(1)
send_buf[0] = SPI_CHIP_ENABLE_CMD
spi.xfer(send_buf)
Wait_Busy_Down(spi)

def Statu_Write(hidDev, cmd, cmdHead):
send_buf = bytearray(FT_MSG_SIZE_FLASH)
send_buf[0] = cmd
send_buf[1:len(cmdHead)+1] = cmdHead
spi.xfer(send_buf)
Wait_Busy_Down(spi)

def CHIP_EXTERN_Reset(spi_downloader, *args):
spi = spi_downloader.spi
RunStatu = True

Id = Read_ID(spi)
# print("flash_id: {:X}".format(Id))

if Id == 0x001340C8 or Id == 0x001640C8:
send_buf = bytearray(1)
CHIP_ENABLE_Command(spi)
send_buf[0] = 0
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)
send_buf[0] = 0
Statu_Write(spi, SPI_STATU_WR_HIG_CMD, send_buf)
elif Id == 0x001440E0 or Id == 0x001423C2:
send_buf = bytearray(2)
CHIP_ENABLE_Command(spi)
send_buf[0] = 0x00
send_buf[1] = 0x08
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)
elif Id == 0x001340E0 or Id == 0x001323C2:
send_buf = bytearray(2)
CHIP_ENABLE_Command(spi)
send_buf[0] = 0x00
send_buf[1] = 0x00
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)
elif Id == 0x00FFFFFF or Id == 0x0:
RunStatu = False
else:
send_buf = bytearray(1)
CHIP_ENABLE_Command(spi)
send_buf[0] = 0
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)

if RunStatu:
time.sleep(0.5)

return RunStatu

def CHIP_EXTERN_Erase(spi_downloader):
spi = spi_downloader.spi
CHIP_ENABLE_Command(spi)
send_buf = bytearray(1)
send_buf[0] = SPI_CHIP_ERASE_CMD
spi.xfer(send_buf)
Wait_Busy_Down(spi)
return True

def CHIP_EXTERN_End(hid_downloader):
spi = spi_downloader.spi
RunStatu = True

Id = Read_ID(spi)

if Id == 0x001340C8 or Id == 0x001640C8:
send_buf = bytearray(1)
CHIP_ENABLE_Command(spi)
send_buf[0] = 0x3c
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)
send_buf[0] = 0x00
Statu_Write(spi, SPI_STATU_WR_HIG_CMD, send_buf)
elif Id == 0x001440E0 or Id == 0x001423C2:
send_buf = bytearray(2)
CHIP_ENABLE_Command(hidDev)
send_buf[0] = 0x3c
send_buf[1] = 0x08
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)
elif Id == 0x001340E0 or Id == 0x001323C2:
send_buf = bytearray(2)
CHIP_ENABLE_Command(spi)
send_buf[0] = 0x3c
send_buf[1] = 0x00
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)
elif Id == 0x00FFFFFF or Id == 0x0:
RunStatu = False
else:
send_buf = bytearray(1)
CHIP_ENABLE_Command(hidDev)
send_buf[0] = 0
Statu_Write(spi, SPI_STATU_WR_LOW_CMD, send_buf)

if RunStatu:
time.sleep(0.5)

return RunStatu

0 comments on commit c600685

Please sign in to comment.