代码见:https://github.com/qinyunti/py32f403-nes.git
一.前言
硬件上有一颗SPI FLASH:W25Q256J, 用于做文件系统存储文件。我们先来实现SPI驱动,然后再来实现FLASH的读写最后移植文件系统。
二.SPI驱动
我们混合使用HAL和LL库,初始化使用HAL,读写使用LL更直接。添加SPI的HAL驱动需要
py32f403_hal_conf.h中
#define HAL_SPI_MODULE_ENABLED
2.1接口设计
考虑有多个SPI和多个CS,所有初始化接口有id参数。
void spi_init(int id, uint32_t baud, int mode);
传输接口有id参数区分不同SPI,CS参数区分共用SPI不同CS。
tx_buffer表示待发送数据,可以为空,此时就发送FF
rx_buffer表示待接受数据,可以为空,
Len表示收发长度
Flag表示是否自动拉高CS。
所以一个接口可以实现只法,只收,或者收发。
uint32_t spi_trans(int id, int cs, uint8_t* tx_buffer, uint8_t* rx_buffer, uint32_t len, int flag);
2.2引脚
PA15 NSS1 FLASH APB2
PB3 SCK AF3
PB4 MISO AF3
PB5 MOSI AF3
PC6 NSS2 TOUCH
PB3/4/5对应SPI1,AF2, PA15使用IO方式控制CS。
还和触摸芯片共享SPI,触摸芯片使用PC6控制CS。
初始化代码如下
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
GPIO_InitStruct.Pin = GPIO_PIN_6;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET);
GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF3_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
2.3SPI配置
初始化代码如下
SPI_HandleTypeDef hspi;
hspi.Instance = SPI1;
uint32_t clk = HAL_RCC_GetPCLK2Freq();
uint32_t div = clk/baud;
if(div<=2){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
}else if(div<=4){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
}else if(div<=8){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
}else if(div<=16){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
}else if(div<=32){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
}else if(div<=64){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
}else if(div<=128){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
}else{
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
}
if(mode & 0x01){
hspi.Init.CLKPhase = SPI_PHASE_2EDGE;
}else{
hspi.Init.CLKPhase = SPI_PHASE_1EDGE;
}
if(mode & 0x02){
hspi.Init.CLKPolarity = SPI_POLARITY_HIGH;
}else{
hspi.Init.CLKPolarity = SPI_POLARITY_LOW;
}
hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi.Init.CRCPolynomial = 0;
hspi.Init.DataSize = SPI_DATASIZE_8BIT;
hspi.Init.Direction = SPI_DIRECTION_2LINES;
hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi.Init.Mode = SPI_MODE_MASTER;
HAL_SPI_Init(&hspi);
LL_SPI_Enable(SPI1);
特别要注意的是HAL_SPI_Init后需要LL_SPI_Enable使能。
2.4传输
可只发,只收(发0xFF),和收发,可以配置是否自动拉高CS用于多次传输组合操作。
if(id == 0){
if(cs == 0){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
}else if(cs == 1){
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET);
}
for(uint32_t i=0; i<len; i++){
if(tx_buffer != (uint8_t*)0){
LL_SPI_TransmitData8(SPI1, tx_buffer);
}else{
LL_SPI_TransmitData8(SPI1, 0xFF);
}
while(LL_SPI_IsActiveFlag_RXNE(SPI1) == 0);
rx = LL_SPI_ReceiveData8(SPI1);
if(rx_buffer != (uint8_t*)0){
rx_buffer = rx;
}
}
if(flag){
if(cs == 0){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
}else if(cs == 1){
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET);
}
}
}else if(id == 1){
}
2.5多线程保护
为了方便多任务使用,设计了互斥量进行保护
初始化时创建互斥量
SemaphoreHandle_t s_spi_mutex[2];
s_spi_mutex[id] = xSemaphoreCreateRecursiveMutex();
传输时先获取互斥量,然后再操作,再释放
if(pdPASS != xSemaphoreTakeRecursive(s_spi_mutex[id], 100)){
return 0;
}
......
xSemaphoreGiveRecursive(s_spi_mutex[id]);
2.6完整源码
Spi.h
#ifndef SPI_H
#define SPI_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
void spi_init(int id, uint32_t baud, int mode);
uint32_t spi_trans(int id, int cs, uint8_t* tx_buffer, uint8_t* rx_buffer, uint32_t len, int flag);
#ifdef __cplusplus
}
#endif
#endif
Spi.c
#include "uart.h"
#include "fifo.h"
#include "py32f4xx_hal.h"
#include "py32f403_ll_spi.h"
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t s_spi_mutex[2];
void spi_init(int id, uint32_t baud, int mode)
{
s_spi_mutex[id] = xSemaphoreCreateRecursiveMutex();
if(id == 0){
/* PA15 NSS1 FLASH APB2
* PB3 SCK AF3
* PB4 MISO AF3
* PB5 MOSI AF3
* PC6 NSS2 TOUCH
*/
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
GPIO_InitStruct.Pin = GPIO_PIN_6;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET);
GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF3_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
SPI_HandleTypeDef hspi;
hspi.Instance = SPI1;
uint32_t clk = HAL_RCC_GetPCLK2Freq();
uint32_t div = clk/baud;
if(div<=2){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
}else if(div<=4){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
}else if(div<=8){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
}else if(div<=16){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
}else if(div<=32){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
}else if(div<=64){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
}else if(div<=128){
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
}else{
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
}
if(mode & 0x01){
hspi.Init.CLKPhase = SPI_PHASE_2EDGE;
}else{
hspi.Init.CLKPhase = SPI_PHASE_1EDGE;
}
if(mode & 0x02){
hspi.Init.CLKPolarity = SPI_POLARITY_HIGH;
}else{
hspi.Init.CLKPolarity = SPI_POLARITY_LOW;
}
hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi.Init.CRCPolynomial = 0;
hspi.Init.DataSize = SPI_DATASIZE_8BIT;
hspi.Init.Direction = SPI_DIRECTION_2LINES;
hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi.Init.Mode = SPI_MODE_MASTER;
HAL_SPI_Init(&hspi);
LL_SPI_Enable(SPI1);
}else if(id == 1){
}
}
uint32_t spi_trans(int id, int cs, uint8_t* tx_buffer, uint8_t* rx_buffer, uint32_t len, int flag)
{
uint8_t rx;
if(pdPASS != xSemaphoreTakeRecursive(s_spi_mutex[id], 100)){
return 0;
}
if(id == 0){
if(cs == 0){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
}else if(cs == 1){
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET);
}
for(uint32_t i=0; i<len; i++){
if(tx_buffer != (uint8_t*)0){
LL_SPI_TransmitData8(SPI1, tx_buffer);
}else{
LL_SPI_TransmitData8(SPI1, 0xFF);
}
while(LL_SPI_IsActiveFlag_RXNE(SPI1) == 0);
rx = LL_SPI_ReceiveData8(SPI1);
if(rx_buffer != (uint8_t*)0){
rx_buffer = rx;
}
}
if(flag){
if(cs == 0){
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
}else if(cs == 1){
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET);
}
}
}else if(id == 1){
}
xSemaphoreGiveRecursive(s_spi_mutex[id]);
return len;
}
三.测试
Main.c中初始化
spi_init(0, 36000000ul, 3);
Shell_func中
#include spi.h
添加两个函数申明
static void spirxfunc(uint8_t* param);
static void spitxrxfunc(uint8_t* param);
g_shell_cmd_list_ast中添加两行,即添加两个命令
{ (uint8_t*)"spirx", spirxfunc, (uint8_t*)"spirx id cs len flag"},
{ (uint8_t*)"spitxrx", spitxrxfunc, (uint8_t*)"spirx id cs hexstr flag"},
对应的实现代码如下
static void spirxfunc(uint8_t* param)
{
int id;
int cs;
int len;
int flag;
uint8_t* rx_tmp;
if(4 == sscanf((const char*)param, "%*s %d %d %d %d", &id, &cs, &len, &flag))
{
xprintf("spitxrx %d %d %d %d\r\n",id,cs,len,flag);
rx_tmp = pvPortMalloc(len);
spi_trans(id,cs,0,rx_tmp,len,flag);
xprintf("[rx]:");
for(int i=0; i<len; i++){
xprintf("%02x ", rx_tmp);
}
xprintf("\r\n");
vPortFree(rx_tmp);
}
else
{
xprintf("param err");
}
}
static void spitxrxfunc(uint8_t* param)
{
int id;
int cs;
uint8_t hexstr[32+1];
uint8_t tx_tmp[16];
uint8_t rx_tmp[16];
uint32_t hexnum;
int flag;
if(4 == sscanf((const char*)param, "%*s %d %d %s %d", &id, &cs, hexstr, &flag))
{
xprintf("spitxrx %d %d %s %d\r\n",id,cs,hexstr,flag);
hexnum = str2hex((const char*)hexstr,tx_tmp,32);
spi_trans(id,cs,tx_tmp,rx_tmp,hexnum,flag);
xprintf("[rx]:");
for(uint32_t i=0; i<hexnum; i++){
xprintf("%02x ", rx_tmp);
}
xprintf("\r\n");
}
else
{
xprintf("param err");
}
}
运行后可以看到添加的命令
查看芯片手册读ID命令为0x9F
所以输入命令
spitxrx 0 0 9fff 1
即发送两字节9FFF,接收两字节。
可以看到收到EF,即对应的ID,说明SPI接口正常。
可以修改
spi_init(0, 36000000ul, 3);
测试频率,模式等参数
这里测试频率26M和72M,示波器抓取的波形
四.总结
以上我们实现了灵活的SPI接口,一个接口实现SPI传输,方便使用,然后基于shell进行了测试。需要注意的是HAL_SPI_Init后需要LL_SPI_Enable使能。