赞
踩
1)实验平台:正点原子APM32E103最小系统板
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
本章将介绍使用APM32E103驱动板载的NOR Flash进行读写操作。通过本章的学习,读者将学习到使用SPI驱动NOR Flash的使用。
本章分为如下几个小节:
41.1 硬件设计
41.2 程序设计
41.3 下载验证
41.1 硬件设计
41.1.1 例程功能
图41.1.3.1 NOR Flash与MCU的连接原理图
41.2 程序设计
41.2.1 Geehy标准库的SPI驱动
本章实验通过SPI2驱动NOR Flash,因此需要对SPI2进行相应的配置,并使用SPI2与NOR Flash进行通信,其具体的步骤如下所示:
①:配置SPI2
②:使能SPI2
③:使用SPI2发送数据(接收数据)前,等待SPI2发送缓冲区为空(接收缓冲区非空)
④:使用SPI2发送一字节数据
⑤:使用SPI2接收一字节数据
在Geehy标准库中对应的驱动函数如下:
①:配置SPI
该函数用于配置SPI,其函数原型如下所示:
void SPI_Config(SPI_T* spi, SPI_Config_T* spiConfig);
该函数的形参描述,如下表所示:
形参 描述
spi 指向SPI外设结构体的指针
例如:SPI1、SPI2等(在apm32e10x.h文件中有定义)
spiConfig 指向SPI配置结构体的指针
需自行定义,并根据SPI的配置参数填充结构体中的成员变量
表41.2.1.1 函数SPI_Config()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表41.2.1.2 函数SPI_Config()返回值描述
该函数使用SPI_Config_T类型的结构体变量传入SPI的配置参数,该结构体的定义如下所示:
typedef enum { SPI_MODE_MASTER = 0x0104, /* 主机模式 */ SPI_MODE_SLAVE = 0x0000 /* 从机模式 */ }SPI_MODE_T; typedef enum { SPI_DATA_LENGTH_16B = 0x0800, /* 8位数据格式 */ SPI_DATA_LENGTH_8B = 0x0000 /* 8位数据格式 */ }SPI_DATA_LENGTH_T; typedef enum { SPI_CLKPHA_1EDGE = 0x0000, /* 在第一个时钟边沿进行采样 */ SPI_CLKPHA_2EDGE = 0x0001 /* 在第二个时钟边沿进行采样 */ }SPI_CLKPHA_T; typedef enum { SPI_CLKPOL_LOW = 0x0000, /* 时钟信号空闲时为低电平 */ SPI_CLKPOL_HIGH = 0x0002 /* 时钟信号空闲时为高电平 */ }SPI_CLKPOL_T; typedef enum { SPI_NSS_SOFT = 0x0200, /* 软件片选 */ SPI_NSS_HARD = 0x0000 /* 硬件片选 */ }SPI_NSS_T; typedef enum { SPI_FIRSTBIT_MSB = 0x0000, /* MSB */ SPI_FIRSTBIT_LSB = 0x0080 /* LSB */ }SPI_FIRSTBIT_T; typedef enum { SPI_DIRECTION_2LINES_FULLDUPLEX = 0x0000, /* 双线全双工 */ SPI_DIRECTION_2LINES_RXONLY = 0x0400, /* 双线仅接收 */ SPI_DIRECTION_1LINE_RX = 0x8000, /* 单线仅接受 */ SPI_DIRECTION_1LINE_TX = 0xC000 /* 单线仅发送 */ }SPI_DIRECTION_T; typedef enum { SPI_BAUDRATE_DIV_2 = 0x0000, /*波特率2分频 */ SPI_BAUDRATE_DIV_4 = 0x0008, /*波特率4分频 */ SPI_BAUDRATE_DIV_8 = 0x0010, /*波特率8分频 */ SPI_BAUDRATE_DIV_16 = 0x0018, /*波特率16分频 */ SPI_BAUDRATE_DIV_32 = 0x0020, /*波特率32分频 */ SPI_BAUDRATE_DIV_64 = 0x0028, /*波特率64分频 */ SPI_BAUDRATE_DIV_128 = 0x0030, /*波特率128分频 */ SPI_BAUDRATE_DIV_256 = 0x0038, /*波特率256分频 */ }SPI_BAUDRATE_DIV_T; typedef struct { SPI_MODE_T mode; /* 模式 */ SPI_DATA_LENGTH_T length; /* 数据位长度 */ SPI_CLKPHA_T phase; /* 采样阶段 */ SPI_CLKPOL_T polarity; /* 时钟线空闲极性 */ SPI_NSS_T nss; /* 片选信号 */ SPI_FIRSTBIT_T firstBit; /* 数据第一比特 */ SPI_DIRECTION_T direction; /* 方向 */ SPI_BAUDRATE_DIV_T baudrateDiv; /* 波特率分频 */ uint16_t crcPolynomial; /* CRC校验值 */ } SPI_Config_T; 该函数的使用示例,如下所示: #include "apm32e10x.h" #include "apm32e10x_spi.h" void example_fun(void) { SPI_Config_T spi_init_struct; /* 配置SPI1 */ spi_init_struct.mode = SPI_MODE_MASTER; spi_init_struct.length = SPI_DATA_LENGTH_8B; spi_init_struct.phase = SPI_CLKPHA_2EDGE; spi_init_struct.polarity = SPI_CLKPOL_HIGH; spi_init_struct.nss = SPI_NSS_SOFT; spi_init_struct.firstBit = SPI_FIRSTBIT_MSB; spi_init_struct.direction = SPI_DIRECTION_2LINES_FULLDUPLEX; spi_init_struct.baudrateDiv = SPI_BAUDRATE_DIV_256; spi_init_struct.crcPolynomial = 7; SPI_Config(SPI1_SPI, &spi_init_struct); }
②:使能SPI
该函数用于使能SPI,其函数原型如下所示:
void SPI_Enable(SPI_T* spi);
该函数的形参描述,如下表所示:
形参 描述
spi 指向SPI外设结构体的指针
例如:SPI1、SPI2等(在apm32e10x.h文件中有定义)
表41.2.1.3 函数SPI_Enable()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表41.2.1.4 函数SPI_Enable()返回值描述
该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_spi.h"
void example_fun(void)
{
/* 使能SPI2 */
SPI_Enable(SPI2);
}
③:读取SPI的状态标志
该函数用于读取SPI的状态标志,其函数原型如下所示:
uint8_t SPI_I2S_ReadStatusFlag(SPI_T* spi, SPI_FLAG_T flag);
该函数的形参描述,如下表所示:
形参 描述
spi 指向SPI外设结构体的指针
例如:SPI1、SPI2等(在apm32e10x.h文件中有定义)
flag 指定的SPI状态标志
例如:SPI_FLAG_RXBNE、SPI_FLAG_TXBE等(在apm32e10x_spi.h文件中有定义)
表41.2.1.5 函数SPI_I2S_ReadStatusFlag()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
SET 事件标志发生
RESET 事件标志未发生
表41.2.1.6 函数SPI_I2S_ReadStatusFlag()返回值描述
该函数的使用示例,如下所示:
#include "apm32e10x.h" #include "apm32e10x_spi.h" void example_fun(void) { uint8_t flag; /* 读取SPI2的接收缓冲区非空标志 */ flag = SPI_I2S_ReadStatusFlag(SPI2, SPI_FLAG_RXBNE); if (flag == SET) { /* Do something. */ } else { /* Do something. */ } }
④:SPI发送数据
该函数用于使用SPI发送数据,其函数原型如下所示:
void SPI_I2S_TxData(SPI_T* spi, uint16_t data);
该函数的形参描述,如下表所示:
形参 描述
spi 指向SPI外设结构体的指针
例如:SPI1、SPI2等(在apm32e10x.h文件中有定义)
data 待发送的数据
表41.2.1.7 函数SPI_I2S_TxData()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表41.2.1.8 函数SPI_I2S_TxData()返回值描述
该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_spi.h"
void example_fun(void)
{
uint8_t data;
data = 0x55;
/* 使用SPI2发送1字节数据 */
SPI_I2S_TxData(SPI2, (uint16_t)data);
}
⑤:SPI接收数据
该函数用于接收SPI接收的数据,其函数原型如下所示:
uint16_t SPI_I2S_RxData(SPI_T* spi);
该函数的形参描述,如下表所示:
形参 描述
spi 指向SPI外设结构体的指针
例如:SPI1、SPI2等(在apm32e10x.h文件中有定义)
表41.2.1.9 函数SPI_I2S_RxData()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
uint16_t类型数据 SPI接收的数据
表41.2.1.10 函数SPI_I2S_TxData()返回值描述
该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_spi.h"
void example_fun(void)
{
uint8_t data;
/* 接收SPI2接收到的1字节数据 */
data = (uint8_t)SPI_I2S_RxData(SPI2);
/* Do something. */
}
41.2.2 SPI驱动
本章实验的SPI驱动主要负责向NOR Flash驱动提供SPI的各种操作函数,例如:SPI初始化、SPI读写等。本章实验中,SPI的驱动代码包括spi.c和spi.h两个文件。
SPI驱动中,对SPI、GPIO相关的宏定义,如下所示:
/* SPI2相关定义 */ #define SPI2_SPI SPI2 #define SPI2_SPI_CLK_ENABLE() do{ RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_SPI2); }while(0) /* SPI2引脚定义 */ #define SPI2_SCK_GPIO_PORT GPIOB #define SPI2_SCK_GPIO_PIN GPIO_PIN_13 #define SPI2_SCK_GPIO_CLK_ENABLE() do{ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB); }while(0) #define SPI2_MISO_GPIO_PORT GPIOB #define SPI2_MISO_GPIO_PIN GPIO_PIN_14 #define SPI2_MISO_GPIO_CLK_ENABLE() do{ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB); }while(0) #define SPI2_MOSI_GPIO_PORT GPIOB #define SPI2_MOSI_GPIO_PIN GPIO_PIN_15 #define SPI2_MOSI_GPIO_CLK_ENABLE() do{ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB); }while(0) SPI驱动中,SPI的初始化函数,如下所示: /** * @brief 初始化SPI2 * @param 无 * @retval 无 */ void spi2_init(void) { GPIO_Config_T gpio_init_struct; SPI_Config_T spi_init_struct; /* 使能时钟 */ SPI2_SPI_CLK_ENABLE(); /* 使能SPI2时钟 */ SPI2_SCK_GPIO_CLK_ENABLE(); /* 使能SPI2 SCK引脚端口时钟 */ SPI2_MISO_GPIO_CLK_ENABLE(); /* 使能SPI2 MISO引脚端口时钟 */ SPI2_MOSI_GPIO_CLK_ENABLE(); /* 使能SPI2 MOSI引脚端口时钟 */ /* 配置SPI2 SCK引脚 */ gpio_init_struct.pin = SPI2_SCK_GPIO_PIN; /* SPI2 SCK引脚 */ gpio_init_struct.speed = GPIO_SPEED_50MHz; /* 高速 */ gpio_init_struct.mode = GPIO_MODE_AF_PP; /* 复用功能推挽输出模式 */ GPIO_Config(SPI2_SCK_GPIO_PORT, &gpio_init_struct); /* 配置SPI2 MISO引脚 */ gpio_init_struct.pin = SPI2_MISO_GPIO_PIN; /* SPI2 MISO引脚 */ gpio_init_struct.speed = GPIO_SPEED_50MHz; /* 高速 */ gpio_init_struct.mode = GPIO_MODE_AF_PP; /* 复用功能推挽输出模式 */ GPIO_Config(SPI2_MISO_GPIO_PORT, &gpio_init_struct); /* 配置SPI2 MOSI引脚 */ gpio_init_struct.pin = SPI2_MOSI_GPIO_PIN; /* SPI2 MOSI引脚 */ gpio_init_struct.speed = GPIO_SPEED_50MHz; /* 高速 */ gpio_init_struct.mode = GPIO_MODE_AF_PP; /* 复用功能推挽输出模式 */ GPIO_Config(SPI2_MOSI_GPIO_PORT, &gpio_init_struct); /* 配置SPI2 */ spi_init_struct.mode = SPI_MODE_MASTER; /* 主模式 */ spi_init_struct.length = SPI_DATA_LENGTH_8B; /* 8位数据帧格式 */ spi_init_struct.phase = SPI_CLKPHA_2EDGE; /* 在第2个时钟边沿采样 */ /* SPI处于空闲状态时,SCK保持高电平状态 */ spi_init_struct.polarity = SPI_CLKPOL_HIGH; spi_init_struct.nss = SPI_NSS_SOFT; /* 启用软件NSS模式 */ spi_init_struct.firstBit = SPI_FIRSTBIT_MSB; /* 先发送最高有效位(MSB) */ /* 双线单向、同时发送和接收 */ spi_init_struct.direction = SPI_DIRECTION_2LINES_FULLDUPLEX; /* 波特率分频系数 */ spi_init_struct.baudrateDiv = SPI_BAUDRATE_DIV_256; spi_init_struct.crcPolynomial = 7; /* CRC多项式数值 */ SPI_Config(SPI2_SPI, &spi_init_struct); /* 配置SPI2 */ SPI_DisableCRC(SPI2_SPI); /* 禁止CRC */ SPI_Enable(SPI2_SPI); /* 使能SPI2 */ }
可以看到,该函数会配置并使能SPI1,同时也配置SPI使用的GPIO引脚的复用功能。
SPI驱动中,使用SPI传输1字节数据的函数,如下所示:
/** * @brief SPI2读写一个字节数据 * @param txdata: 待发送的一字节数据 * @retval 接收到的一字节数据 */ uint8_t spi2_read_write_byte(uint8_t txdata) { uint8_t rxdata; /* 等待发送缓冲器为空 */ while (SPI_I2S_ReadStatusFlag(SPI2_SPI, SPI_FLAG_TXBE) != SET); /* 发送一字节数据 */ SPI_I2S_TxData(SPI2_SPI, txdata); /* 等待接收缓冲非空 */ while (SPI_I2S_ReadStatusFlag(SPI2_SPI, SPI_FLAG_RXBNE) != SET); /* 接收一字节数据 */ rxdata = SPI_I2S_RxData(SPI2_SPI); return rxdata; }
使用SPI传输1字节数据就是先后发送并接收1字节数据。
41.2.3 NOR Flash驱动
本章实验的NOR Flash驱动主要负责向应用层提供NOR Flash的初始化和读写操作等函数。本章实验中,NOR Flash的驱动代码包括norflash.c和norflash.h两个文件。
NOR Flash驱动中,对GPIO的相关宏定义,如下所示:
/* NOR Flash片选引脚定义 */ #define NORFLASH_CS_GPIO_PORT GPIOB #define NORFLASH_CS_GPIO_PIN GPIO_PIN_12 #define NORFLASH_CS_GPIO_CLK_ENABLE() do{ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB); }while(0) /* NOR Flash片选引脚IO操作 */ #define NORFLASH_CS(x) do{ x ? \ GPIO_SetBit(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN) : \ GPIO_ResetBit(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN); \ }while(0) NOR Flash驱动中,初始化NOR Flash的函数,如下所示: /** * @brief 初始化NOR Flash * @param 无 * @retval 无 */ void norflash_init(void) { GPIO_Config_T gpio_init_struct; uint8_t temp; /* 使能NOR Flash片选引脚端口时钟 */ NORFLASH_CS_GPIO_CLK_ENABLE(); /* NOR Flash片选引脚 */ gpio_init_struct.pin = NORFLASH_CS_GPIO_PIN; gpio_init_struct.speed = GPIO_SPEED_50MHz; /* 高速 */ gpio_init_struct.mode = GPIO_MODE_OUT_PP; /* 推挽输出模式 */ GPIO_Config(NORFLASH_CS_GPIO_PORT, &gpio_init_struct); NORFLASH_CS(1); /* 失能NOR Flash片选 */ /* 配置SPI2接口 */ spi2_init(); /* 初始化SPI2 */ /* 设置SPI2速度,60MHz/2=30MHz */ spi2_set_speed(SPI_SPEED_2); /* 读取NOR Flash芯片ID */ g_norflash_type = norflash_read_id(); /* W25Q256需使能4字节地址模式 */ if (g_norflash_type == W25Q256) { /* 读状态寄存器3,判断地址模式 */ temp = norflash_read_sr(3); /* 如果不是4字节地址模式,则需进行相应配置 */ if ((temp & 0x01) == 0) { norflash_write_enable(); /* NOR Flash写使能 */ /* ADP=1,配置上电4字节地址模式 */ temp |= (1 << 1); norflash_write_sr(3, temp); /* 写状态寄存器3 */ NORFLASH_CS(0); /* 使能NOR Flash片选 */ /* 使能4字节地址模式 */ spi2_read_write_byte(NORFLASH_Enable4ByteAddr); NORFLASH_CS(1); /* 失能NOR Flash片选 */ } } }
可以看到,在NOR Flash的初始化函数中,先初始化了控制NOR Flash片选的GPIO引脚,然后再是初始化与NOR Flash通讯的SPI并配置其通讯波特率,最后还会根据不同容量的NOR Flash做相应的配置操作。
NOR Flash驱动中其他对NOR Flash的操作函数,例如,NOR Flash的读写函数、擦除函数等,请读者结合25Q128 NOR Flash芯片的数据手册查看本实验的配套实验源码。
41.2.4 实验应用代码
本章实验的应用代码,如下所示:
/* 待写入NOR Flash的数据 */ static const uint8_t g_text_buf[] = {"APM32 SPI TEST"}; /* 待写入NOR Flash数据的长度 */ #define TEXT_SIZE sizeof(g_text_buf) int main(void) { uint16_t id; uint8_t t = 0; uint8_t key; uint8_t data[TEXT_SIZE]; uint32_t flashsize; NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4); /* 设置中断优先级分组为组4 */ sys_apm32_clock_init(15); /* 配置系统时钟 */ delay_init(120); /* 初始化延时功能 */ usart_init(115200); /* 初始化串口 */ usmart_dev.init(120); /* 初始化USMART */ led_init(); /* 初始化LED */ lcd_init(); /* 初始化LCD */ norflash_init(); /* 初始化NOR Flash */ lcd_show_string(30, 50, 200, 16, 16, "APM32", RED); lcd_show_string(30, 70, 200, 16, 16, "SPI TEST", RED); lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED); lcd_show_string(30, 110, 200, 16, 16, "KEY_UP:Write KEY0:Read", RED); id = norflash_read_id(); /* 读NOR Flash芯片ID */ while ((id == 0) || (id == 0xFFFF)) /* 检测不到NOR Flash芯片 */ { lcd_show_string(30, 130, 200, 16, 16, "NOR Flash Check Failed!", RED); delay_ms(500); lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED); delay_ms(500); LED0_TOGGLE(); } /* NOR Flash检测正常 */ lcd_show_string(30, 130, 200, 16, 16, "NOR FLASH Ready!", BLUE); /* NOR Flash容量为16MB */ flashsize = 16 * 1024 * 1204; while (1) { t++; key = key_scan(0); if (key == WKUP_PRES) /* 写入数据 */ { lcd_fill(0, 150, 239, 319, WHITE); lcd_show_string(30, 150, 200, 16, 16, "Start Write Flash....", BLUE); sprintf((char *)data, "%s%d", (char *)g_text_buf, t); /* 从倒数第100个地址处开始写入TEXT_SIZE个字节的数据 */ norflash_write((uint8_t *)data, flashsize - 100, TEXT_SIZE); lcd_show_string(30, 150, 200, 16, 16, "Flash Write Finished!", BLUE); } else if (key == KEY0_PRES) /* 读取数据 */ { lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH... . ", BLUE); /* 从倒数第100个地址处开始读出TEXT_SIZE个字节的数据 */ norflash_read(data, flashsize - 100, TEXT_SIZE); lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is: ", BLUE); lcd_show_string(30, 170, 200, 16, 16, (char *)data, BLUE); } if (t == 20) { LED0_TOGGLE(); t = 0; } delay_ms(10); } }
从本章实验的应用代码中可以看到,在初始化完NOR Flash后,会检测与NOR Flash的连接是否正常,若与NOR Flash的连接正常,则会不断地等待按键输入,若检测到KEY_UP按键被按下,则会往NOR Flash的指定地址中写入指定的数据,若检测KEY0按键被按下,则会从NOR Flash的指定地址中读取数据,并在LCD上进行显示。
41.3 下载验证
在完成编译和烧录操作后,若MCU与NOR Flash的连接无误,则可以在LCD上看到“NOR Flash Ready!”的提示信息,此时可以按下KEY_UP按键往NOR Flash的指定地址写入指定的数据,然后再按下KEY_0按键从NOR Flash的指定地址将写入的数据读回来在LCD上进行显示,此时便可以看到LCD上显示“APM32 SPI TESTn”的提示信息,该提示信息就是从NOR Flash中读回的数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。