本人采用的是正点原子的精英STM32F103开发板,其包含一块W25Q128型号的flash芯片。该flash与STM32F103的SPI2相连。
下面根据正点原子提供的开发指南文档,实现FreeRTOS的SPI实验版本。
芯片型号为:STM32F103ZET6
启用的模块有USART1,SPI2,其中SPI2的CS片选采用软件控制,是PB12管脚。
此外,为了触发读写flash操作,这里使用KEY0(PE4)作为触发开关。
1. 启动STM32CubeMX选择芯片型号
打开CubeMX后,在左侧搜索框输入型号STM32F103,即可看到有两种型号,选择匹配的STM32F103ZETx即可。
2. 选好芯片型号,点击右上角start project,开始配置
1)配置USART1
在左上角的搜索框,输入usart,选择USART1,并按照下图进行配置(未截图表示无改动):
2)对SPI2进行配置
时钟极性CPOL=1,串行同步时钟的空闲状态为高电平;
时钟相位CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样;
CRC 值计算的多项式的值参照教程设置为7,也就是X0+X1+X2,可参照STM32CubeMX工具配置SPI的CRC时数字和表达式转换。亲测,其实使用默认的X1+X3也是可以的,应该是值只要大于1即可,什么表达式都可以。
3)对GPIO管脚进行配置
KEY0(PE4)作为触发开关,配置为input;
PB12作为SPI12的片选CS,配置为output。
3.时钟配置
时钟部分暂时不动,按照默认配置即可。
4.选择生成代码格式
1)保存工程
点击左上角file-->save,选择路径,保存工程
2)生成初始代码
根据使用的开发工具IDE类型,选择对应的生成格式,点击generate,完成初始代码的生成。
5.usart1功能测试
#include <stdio.h>
#include <string.h>
//....
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t msg[100] = {0};
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
sprintf((char*)msg, "hello world\r\n");
HAL_UART_Transmit(&huart1, msg, sizeof(msg), 100);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
6.KEY0(PE4)功能验证
#include <stdio.h>
#include <string.h>
//......
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t msg[100] = {0};
uint8_t val = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
memset(msg,0,100);
val = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4);
if(0 == val)
{
sprintf((char*)msg, "current val is %d, key0 is pressed\r\n", val);
HAL_UART_Transmit(&huart1, msg, sizeof(msg), 100);
}
else
{
sprintf((char*)msg, "current val is %d, key0 is not pressed\r\n", val);
HAL_UART_Transmit(&huart1, msg, sizeof(msg), 100);
}
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
这里需要注意KEY0的值默认为1,按压后的值为0。
上图即为上电后,按压前后的打印信息。
7.flash读写测试
读flash很简单,仅需要执行spi对应的指令即可;
写flash较为复杂,首先要知道,flash不支持覆盖写,仅支持擦除后再写,原因是flash的写操作只能将1变为0,不能将0变为1,若想将0变为1则只能通过擦除操作,所以写flash时,如果对应的位置值不是全1,则需要先执行擦除操作,再执行写操作。此外,W25Q128 将 16M 的容量分为 256 个块(Block),每个块大小为64K字节,每个块又分为16个扇区(Sector),每个扇区4K个字。W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。所以在程序中定义一个至少4K的数组变量,用于保存待擦除扇区的内容。
#include <stdio.h>
#include <string.h>
//....
void spi_read_flash(uint8_t* pbuffer, uint32_t read_addr, uint16_t read_len)
{
uint8_t send_cmd;
uint8_t recv_cmd;
uint16_t i;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
send_cmd = 0x03;
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd = (uint8_t)(read_addr>>16);
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd = (uint8_t)(read_addr>>8);
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd = (uint8_t)read_addr;
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd = 0xff;
for(i=0;i<read_len;i++)
{
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
pbuffer[i] = recv_cmd;
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}
uint8_t flash_buffer[4096];
void flash_write_page(uint8_t* pbuffer, uint32_t write_addr, uint16_t write_len)
{
uint16_t i=0;
uint8_t send_cmd = 0;
uint8_t recv_cmd = 0;
// write enable
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
send_cmd=0x06;
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
// write page
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
send_cmd=0x02;
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd=(uint8_t)(write_addr>>16);
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd=(uint8_t)(write_addr>>8);
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd=(uint8_t)write_addr;
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
for(i=0;i<write_len;i++)
{
send_cmd=pbuffer[i];
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY);
}
void flash_erase(uint32_t fan_addr)
{
uint8_t send_cmd = 0;
uint8_t recv_cmd = 0;
fan_addr *=4096;
send_cmd=0x06;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
send_cmd=0x20;
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd=(uint8_t)(fan_addr>>16);
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd=(uint8_t)(fan_addr>>8);
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
send_cmd=(uint8_t)fan_addr;
HAL_SPI_TransmitReceive(&hspi2, &send_cmd, &recv_cmd, 1, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY);
}
void flash_write(uint8_t* pbuffer, uint32_t write_addr, uint16_t write_len)
{
uint16_t page_remain;
page_remain = 256 - write_addr%256;
if(write_len<=page_remain) page_remain=write_len;
while(1)
{
flash_write_page(pbuffer, write_addr, write_len);
if(write_len == page_remain)
{
break;
}
else
{
pbuffer+=page_remain;
write_addr+=page_remain;
write_len-=page_remain;
if(write_len>256)
{
page_remain=256;
}
else
{
page_remain = write_len;
}
}
}
}
void spi_write_flash(uint8_t* pbuffer, uint32_t write_addr, uint16_t write_len)
{
uint32_t sector_index;
uint16_t sector_offset;
uint16_t sector_remain;
uint16_t i = 0;
sector_index = write_addr/4096;
sector_offset = write_addr%4096;
sector_remain = 4096 - sector_offset;
if(write_len <= sector_remain) sector_remain = write_len;
while(1)
{
spi_read_flash(flash_buffer, sector_index*4096, 4096);
for(i=0;i<sector_remain;i++)
{
if(flash_buffer[sector_offset+i]!=0xff)break;
}
if(i<sector_remain)
{
flash_erase(sector_index);
for(i=0;i<sector_remain;i++)
{
flash_buffer[sector_offset + i] = pbuffer[i];
}
flash_write(flash_buffer, sector_index*4096, 4096);
}
else
{
flash_write(pbuffer, write_addr, sector_remain);
}
if(write_len == sector_remain)
{
break;
}
else
{
sector_index++;
sector_offset=0;
pbuffer+=sector_remain;
write_addr+=sector_remain;
write_len-=sector_remain;
if(write_len > 4096)
{
sector_remain=4096;
}
else
{
sector_remain=write_len;
}
}
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t msg[100] = {0};
uint8_t val = 0;
uint32_t flash_size = 128*1024*1024; //FLASH ´óСΪ16M×Ö½Ú
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_Delay(3000);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
memset(msg,0,100);
val = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4);
if(0 == val)
{
sprintf((char*)msg, "33333333333333333333\r\n");
HAL_UART_Transmit(&huart1, msg, sizeof(msg), 100);
spi_write_flash(msg, flash_size-100, sizeof(msg));
}
else
{
sprintf((char*)msg, "key0 not pressed:");
HAL_UART_Transmit(&huart1, msg, sizeof(msg), 100);
spi_read_flash(msg,flash_size-100,30);
HAL_UART_Transmit(&huart1, msg, sizeof(msg), 100);
}
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
上电设备后,增加了HAL_Delay(3000),要不然好像KEY0状态会有跳变。
之前已在flash中写入了222222,按下KEY0后,写入33333,松开KEY0,再次读取flash中的值,可以看到发生了变化。
8.总结
使用STM32CubeMX配置USART、GPIO和SPI十分傻瓜,不过也需要对配置参数有一定的认知。配置好后生成的代码基本不再需要更改,可直接使用FreeRTOS的库函数进行操作。不过这样可能并不适合初学者学习理解每个接口的功能,且库函数有时并不能满足特定的使用场景,仍然需要修改删减代码。