2 logging to a DataFlash block based storage device on SPI
6 #include <AP_HAL/AP_HAL.h>
8 #include "AP_Logger_W25NXX.h"
10 #if HAL_LOGGING_FLASH_W25NXX_ENABLED
14 extern const AP_HAL::HAL
& hal
;
16 #define JEDEC_WRITE_ENABLE 0x06
17 #define JEDEC_WRITE_DISABLE 0x04
18 #define JEDEC_READ_STATUS 0x05
19 #define JEDEC_WRITE_STATUS 0x01
20 #define JEDEC_READ_DATA 0x03
21 #define JEDEC_PAGE_DATA_READ 0x13
22 #define JEDEC_FAST_READ 0x0b
23 #define JEDEC_DEVICE_ID 0x9F
24 #define JEDEC_PAGE_WRITE 0x02
25 #define JEDEC_PROGRAM_EXECUTE 0x10
27 #define JEDEC_DEVICE_RESET 0xFF
28 #define JEDEC_BLOCK_ERASE 0xD8 // 128K erase
30 #define JEDEC_STATUS_BUSY 0x01
31 #define JEDEC_STATUS_WRITEPROTECT 0x02
33 #define W25NXX_STATUS_REG 0xC0
34 #define W25NXX_PROT_REG 0xA0
35 #define W25NXX_CONF_REG 0xB0
36 #define W25NXX_STATUS_EFAIL 0x04
37 #define W25NXX_STATUS_PFAIL 0x08
39 #define W25NXX_PROT_SRP1_ENABLE (1 << 0)
40 #define W25NXX_PROT_WP_E_ENABLE (1 << 1)
41 #define W25NXX_PROT_TB_ENABLE (1 << 2)
42 #define W25NXX_PROT_PB0_ENABLE (1 << 3)
43 #define W25NXX_PROT_PB1_ENABLE (1 << 4)
44 #define W25NXX_PROT_PB2_ENABLE (1 << 5)
45 #define W25NXX_PROT_PB3_ENABLE (1 << 6)
46 #define W25NXX_PROT_SRP2_ENABLE (1 << 7)
48 #define W25NXX_CONFIG_ECC_ENABLE (1 << 4)
49 #define W25NXX_CONFIG_BUFFER_READ_MODE (1 << 3)
51 #define W25NXX_TIMEOUT_PAGE_READ_US 60 // tREmax = 60us (ECC enabled)
52 #define W25NXX_TIMEOUT_PAGE_PROGRAM_US 700 // tPPmax = 700us
53 #define W25NXX_TIMEOUT_BLOCK_ERASE_MS 10 // tBEmax = 10ms
54 #define W25NXX_TIMEOUT_RESET_MS 500 // tRSTmax = 500ms
56 #define W25N01G_NUM_BLOCKS 1024
57 #define W25N02K_NUM_BLOCKS 2048
59 #define JEDEC_ID_WINBOND_W25N01GV 0xEFAA21
60 #define JEDEC_ID_WINBOND_W25N02KV 0xEFAA22
62 void AP_Logger_W25NXX::Init()
64 dev
= hal
.spi
->get_device("dataflash");
66 AP_HAL::panic("PANIC: AP_Logger W25NXX device not found");
70 dev_sem
= dev
->get_semaphore();
72 if (!getSectorCount()) {
82 WITH_SEMAPHORE(dev_sem
);
83 uint8_t b
= JEDEC_DEVICE_RESET
;
84 dev
->transfer(&b
, 1, nullptr, 0);
86 hal
.scheduler
->delay(W25NXX_TIMEOUT_RESET_MS
);
88 // disable write protection
89 WriteStatusReg(W25NXX_PROT_REG
, 0);
90 // enable ECC and buffer mode
91 WriteStatusReg(W25NXX_CONF_REG
, W25NXX_CONFIG_ECC_ENABLE
|W25NXX_CONFIG_BUFFER_READ_MODE
);
93 printf("W25NXX status: SR-1=0x%x, SR-2=0x%x, SR-3=0x%x\n",
94 ReadStatusRegBits(W25NXX_PROT_REG
),
95 ReadStatusRegBits(W25NXX_CONF_REG
),
96 ReadStatusRegBits(W25NXX_STATUS_REG
));
98 AP_Logger_Block::Init();
102 wait for busy flag to be cleared
104 void AP_Logger_W25NXX::WaitReady()
110 uint32_t t
= AP_HAL::millis();
112 hal
.scheduler
->delay_microseconds(100);
113 if (AP_HAL::millis() - t
> 5000) {
114 printf("DataFlash: flash_died\n");
121 bool AP_Logger_W25NXX::getSectorCount(void)
125 WITH_SEMAPHORE(dev_sem
);
127 // Read manufacturer ID
128 uint8_t cmd
= JEDEC_DEVICE_ID
;
129 uint8_t buf
[4]; // buffer not yet allocated
130 dev
->transfer(&cmd
, 1, buf
, 4);
132 uint32_t id
= buf
[1] << 16 | buf
[2] << 8 | buf
[3];
135 case JEDEC_ID_WINBOND_W25N01GV
:
137 df_PagePerBlock
= 64;
138 df_PagePerSector
= 64; // make sectors equivalent to block
139 flash_blockNum
= W25N01G_NUM_BLOCKS
;
141 case JEDEC_ID_WINBOND_W25N02KV
:
143 df_PagePerBlock
= 64;
144 df_PagePerSector
= 64; // make sectors equivalent to block
145 flash_blockNum
= W25N02K_NUM_BLOCKS
;
149 hal
.scheduler
->delay(2000);
150 printf("Unknown SPI Flash 0x%08x\n", id
);
154 df_NumPages
= flash_blockNum
* df_PagePerBlock
;
156 printf("SPI Flash 0x%08x found pages=%u\n", id
, df_NumPages
);
160 // Read the status register bits
161 uint8_t AP_Logger_W25NXX::ReadStatusRegBits(uint8_t bits
)
163 WITH_SEMAPHORE(dev_sem
);
164 uint8_t cmd
[2] { JEDEC_READ_STATUS
, bits
};
166 dev
->transfer(cmd
, 2, &status
, 1);
170 void AP_Logger_W25NXX::WriteStatusReg(uint8_t reg
, uint8_t bits
)
173 WITH_SEMAPHORE(dev_sem
);
174 uint8_t cmd
[3] = {JEDEC_WRITE_STATUS
, reg
, bits
};
175 dev
->transfer(cmd
, 3, nullptr, 0);
178 bool AP_Logger_W25NXX::Busy()
180 uint8_t status
= ReadStatusRegBits(W25NXX_STATUS_REG
);
182 if ((status
& W25NXX_STATUS_PFAIL
) != 0) {
183 printf("Program failure!\n");
185 if ((status
& W25NXX_STATUS_EFAIL
) != 0) {
186 printf("Erase failure!\n");
189 return (status
& JEDEC_STATUS_BUSY
) != 0;
193 send a command with an address
195 void AP_Logger_W25NXX::send_command_addr(uint8_t command
, uint32_t PageAdr
)
199 cmd
[1] = (PageAdr
>> 16) & 0xff;
200 cmd
[2] = (PageAdr
>> 8) & 0xff;
201 cmd
[3] = (PageAdr
>> 0) & 0xff;
203 dev
->transfer(cmd
, 4, nullptr, 0);
206 void AP_Logger_W25NXX::PageToBuffer(uint32_t pageNum
)
208 if (pageNum
== 0 || pageNum
> df_NumPages
+1) {
209 printf("Invalid page read %u\n", pageNum
);
210 memset(buffer
, 0xFF, df_PageSize
);
211 df_Read_PageAdr
= pageNum
;
215 // we already just read this page
216 if (pageNum
== df_Read_PageAdr
&& read_cache_valid
) {
220 df_Read_PageAdr
= pageNum
;
224 uint32_t PageAdr
= (pageNum
-1);
227 WITH_SEMAPHORE(dev_sem
);
228 // read page into internal buffer
229 send_command_addr(JEDEC_PAGE_DATA_READ
, PageAdr
);
232 // read from internal buffer into our buffer
235 WITH_SEMAPHORE(dev_sem
);
236 dev
->set_chip_select(true);
238 cmd
[0] = JEDEC_READ_DATA
;
239 cmd
[1] = (0 >> 8) & 0xff; // column address zero
240 cmd
[2] = (0 >> 0) & 0xff; // column address zero
242 dev
->transfer(cmd
, 4, nullptr, 0);
243 dev
->transfer(nullptr, 0, buffer
, df_PageSize
);
244 dev
->set_chip_select(false);
246 read_cache_valid
= true;
250 //#define AP_W25NXX_DEBUG
251 #ifdef AP_W25NXX_DEBUG
252 static uint32_t block_writes
;
253 static uint32_t last_write_msg_ms
;
255 void AP_Logger_W25NXX::BufferToPage(uint32_t pageNum
)
257 if (pageNum
== 0 || pageNum
> df_NumPages
+1) {
258 printf("Invalid page write %u\n", pageNum
);
262 // just wrote the cached page
263 if (pageNum
!= df_Read_PageAdr
) {
264 read_cache_valid
= false;
269 uint32_t PageAdr
= (pageNum
-1);
271 WITH_SEMAPHORE(dev_sem
);
273 // write our buffer into internal buffer
274 dev
->set_chip_select(true);
277 cmd
[0] = JEDEC_PAGE_WRITE
;
278 cmd
[1] = (0 >> 8) & 0xff; // column address zero
279 cmd
[2] = (0 >> 0) & 0xff; // column address zero
281 dev
->transfer(cmd
, 3, nullptr, 0);
282 dev
->transfer(buffer
, df_PageSize
, nullptr, 0);
283 dev
->set_chip_select(false);
286 // write from internal buffer into page
288 WITH_SEMAPHORE(dev_sem
);
289 send_command_addr(JEDEC_PROGRAM_EXECUTE
, PageAdr
);
292 #ifdef AP_W25NXX_DEBUG
294 if (AP_HAL::millis() - last_write_msg_ms
> 5000) {
295 hal
.console
->printf("W25NXX: writes %lukB/s, pages %lu/s\n", (block_writes
*df_PageSize
)/(5*1024), block_writes
/5);
297 last_write_msg_ms
= AP_HAL::millis();
303 erase one sector (sizes varies with hw)
305 void AP_Logger_W25NXX::SectorErase(uint32_t blockNum
)
308 WITH_SEMAPHORE(dev_sem
);
310 uint32_t PageAdr
= blockNum
* df_PagePerBlock
;
311 send_command_addr(JEDEC_BLOCK_ERASE
, PageAdr
);
317 void AP_Logger_W25NXX::Sector4kErase(uint32_t sectorNum
)
319 SectorErase(sectorNum
);
322 void AP_Logger_W25NXX::StartErase()
326 WITH_SEMAPHORE(dev_sem
);
328 // just erase the first block, others will follow in InErase
329 send_command_addr(JEDEC_BLOCK_ERASE
, 0);
332 erase_start_ms
= AP_HAL::millis();
333 printf("Dataflash: erase started\n");
336 bool AP_Logger_W25NXX::InErase()
338 if (erase_start_ms
&& !Busy()) {
339 if (erase_block
< flash_blockNum
) {
340 SectorErase(erase_block
++);
342 printf("Dataflash: erase done (%u ms)\n", AP_HAL::millis() - erase_start_ms
);
347 return erase_start_ms
!= 0;
350 void AP_Logger_W25NXX::WriteEnable(void)
353 WITH_SEMAPHORE(dev_sem
);
354 uint8_t b
= JEDEC_WRITE_ENABLE
;
355 dev
->transfer(&b
, 1, nullptr, 0);
358 #endif // HAL_LOGGING_FLASH_W25NXX_ENABLED