前言
此篇將使用STM32透過UART下指令來對SPI Flash做 Erase/Write/Read 等動作
UART下指令的部分要借助 Command Line Interface - CLI via UART 這一篇內容;但MCU和I/O腳位不同
Serial NOR Flash
Nor Flash支援SPI/Dual SPI/Quad SPI三種spi傳輸規格,上電後預設是走SPI mode
從spec看來SPI的傳輸速度可以到達104MHz,Dual mode有208MHz,Quad mode有416MHz
三種mode在腳位定義上,從pin 1開始
- Standard SPI:/CS,DO,/WP,GND,DI,CLK,/HOLD,VCC
- Dual SPI :/CS,IO1,/WP,GND,IO0,CLK,/HOLD,VCC
- Quad SPI :/CS,IO1,IO2,GND,IO0,CLK,IO3,VCC
PIN DESCRIPTION (SPI mode)
PIN NO. | PIN NAME | I/O | FUNCTION |
1 | /CS | I | Chip Select Input |
2 | DO | O | Data Output |
3 | /WP | I | Write Protect Input (特別要注意,/WP是用來保護Status Register狀態暫存器的;對data無保護作用) |
4 | GND | Ground | |
5 | DI | I | Data Input |
6 | CLK | I | Serial Clock Input |
7 | /HOLD | I | Hold Input ( /HOLD為LOW時 無法接收任何command或data) |
8 | VCC | Power Supply |
Instruction Set 指令表
Instruction Set 指令表解析
0x9F JEDEC ID
讀取flash的manufacture id和device id
我個人習慣程式一開始先讀取0x9F確認SPI介面和flash能正常溝通,flash是否能作動
9F 00 00 00
00 EF 40 13//read back
0x03 Read Data
讀取資料,0x03指令後接3個byte的記憶體讀取起始點,然後就可以依序一直讀取data
03 12 34 56 00 00 00//讀取位址0x123456開始的3個byte
00 00 00 00 XX XX XX//"XX" means data
0x06 Write Enable
開啟寫入功能
無論是 Erase或Write(Program)在執行前都須先執行0x06開啟寫入
0xC7/0x60 Chip Erase
06//write enable
C7//erase chip
0x20 Erase Sector (4KB)
06//write enable
20 00 00 00//erase sector from 0x000000
0x02 Program (Write)
06//write enable
02 00 00 00 11 22 33//write in 0x11 0x22 0x33 start from 0x000000
🚨 要注意的事,NOR flash在寫入data前一定要記得先erase memory,
erase後記憶體就會變成0xFF,寫作的動作就是把bit從1變0 或不變
操作環境
- Board:NUCLEO-F401RE
- IDE:STM32CubeIDE
- Code:STM32CubeMX
- Flash:W25Q40CLSNIG
MCU I/O配置
- UART:TX(PA9),RX(PA10)
- SPI:MOSI(PA7),MISO(PA6),SCK(PA5),CS(PB6),WP(PA8),HOLD(PB10)
1. STM32CubeMX
勾選USART2,選 Asynchronous,bardrate:115200bps
勾選USART2的中斷功能
勾選SPI1,
Flash支持SPI (mode 0及3),設定上就使用預設的mode 0 (CPOL:LOW,CPHA:1 Edge第一個邊緣變化)
Prescaler選64,SPI1的clock source使用APB2,目前APB2為84MHz,所以SPI1的速度為84/64=1.3MHz
勾選SPI1中斷功能
新增GPIO WP/HOLD/CS 為output push pull
2. STM32CubeIDE
首先先是完成UART CLI的功能
main.c
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
uint8_t uart_rx[2];
char cli_data[80];
char *cli_pointer = cli_data;
uint8_t cli_key_enter;
void CLI(void)
{
static uint8_t values[20];
uint8_t length = 0;
char *token;
uint8_t command = 0;
// extern USBD_HandleTypeDef hUsbDeviceFS;
if(cli_key_enter != 1) return;
cli_key_enter = 0;
token = strtok(cli_data, " ");
// while (token != NULL) {
// //printf("%s\n", token);
// token = strtok(NULL, " ");
// }
while(token != NULL)
{
if((strcmp(token, "WP") == 0) ||(strcmp(token, "wp") == 0))
{
command = 0x01;
length++;
}else if((strcmp(token, "CS") == 0) ||(strcmp(token, "cs") == 0))
{
command = 0x02;
length++;
}else if((strcmp(token, "HOLD") == 0) ||(strcmp(token, "hold") == 0))
{
command = 0x03;
length++;
}else{
values[length++] = strtol(token, NULL, 16);
}
//values[length++] = atoi( token );
token = strtok(NULL, " ");
}
if(length>0)
{
switch(command)
{
case 0x01://WP
if(length == 1){
printf("WP:%d\r\n", HAL_GPIO_ReadPin(WP_GPIO_Port, WP_Pin));
}else if(length >= 2){
if(values[1] == 0)
{
WP(0);
}else if(values[1] == 1)
{
WP(1);
}
}
break;
case 0x02://CS
if(length == 1){
printf("CS:%d\r\n", HAL_GPIO_ReadPin(CS_GPIO_Port, CS_Pin));
}else if(length >= 2){
if(values[1] == 0)
{
CS(0);
}else if(values[1] == 1)
{
CS(1);
}
}
break;
case 0x03://HOLD
if(length == 1){
printf("HOLD:%d\r\n", HAL_GPIO_ReadPin(HOLD_GPIO_Port, HOLD_Pin));
}else if(length >= 2){
if(values[1] == 0)
{
HOLD(0);
}else if(values[1] == 1)
{
HOLD(1);
}
}
break;
default://SPI
printf("%d\r\n", length);
CS(0);
spi_count = length;
HAL_SPI_TransmitReceive_IT(&hspi1, values, spi_rx, spi_count);
break;
}
}else{
printf("--- CLI console ---\r\n");
printf("Key in multi hex values A0 00 10 and press ENTER to execute\r\n");
printf("-------------------\r\n");
}
cli_pointer = cli_data;
memset(cli_data, 0, sizeof(cli_data));
}
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart2, uart_rx, 1);
printf("Halo Flash\r\n");
/* USER CODE END 2 */
while(1)
{
/* USER CODE BEGIN 3 */
CLI();
}
}
UART中斷程序,用來判斷接收字元
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(&huart2, uart_rx, 1);
HAL_UART_Transmit(&huart2, uart_rx, 1, HAL_MAX_DELAY);
switch(uart_rx[0])
{
case '\r'://"Enter 0x13"
printf("\n");
cli_key_enter = 1;
break;
case 0x7F://"backspace"
if(cli_pointer > cli_data)
{
*(cli_pointer) = 0;
cli_pointer--;
}
break;
case 0x1B://"ESC"
cli_pointer = cli_data;
memset(cli_data, 0, 3);
printf("\r");
break;
default:
if((cli_pointer - cli_data) >=80) cli_pointer = cli_data;
*(cli_pointer++) = (char)uart_rx[0];
break;
}
// int size = (cli_pointer) - (cli_data);
}
/* USER CODE END 4 */
完成後,上電可以看到 Halo Flash,按 Enter則出現訊息
再來完成SPI的程式
main.c
SPI的發送/接收buffer大小設定20個byte,使用者以自己的需求做調整
/* USER CODE BEGIN PD */
#define CS(x) HAL_GPIO_WritePin(HOLD_GPIO_Port, CS_Pin, x)
#define WP(x) HAL_GPIO_WritePin(WP_GPIO_Port, WP_Pin, x)
#define HOLD(x) HAL_GPIO_WritePin(CS_GPIO_Port, HOLD_Pin, x)
/* USER CODE END PD */
/* USER CODE BEGIN 0 */
uint8_t spi_tx[20];
uint8_t spi_rx[20];
uint8_t spi_count;
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 2 */
CS(1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
}
}
SPI完成傳輸中斷程序
/* USER CODE BEGIN 4 */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
int i = 0;
CS(1);
while((spi_count >0)&&(spi_count >i)){
printf("%02X ", spi_rx[i]);
i++;
}
printf("\r\n");
spi_count = 0;
}
/* USER CODE END 4 */