libftdi1学习笔记 4 - MPSSE SPI

2024-04-16 21:20
文章标签 学习 笔记 spi libftdi1 mpsse

本文主要是介绍libftdi1学习笔记 4 - MPSSE SPI,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1. 初始化

2. SCK默认电平设置

3. GPIO控制

4. spi全双工通信

4.1 MSB/LSB

4.2 分配command缓存

4.3 spi0TransferBit

4.3 spi1TransferBit

4.4 spi2TransferBit

4.5 spi3TransferBit

4.6 写命令序列

4.7 读数据

4.8 组合实际数据

5. 验证

5.1 初始化FTDI设备模式

5.2 初始化GPIO

5.3 控制CS

5.4 读flash的id

5.5 结果


与官方的方式不同,这里采用纯GPIO模式的方式实现SPI,这样可以定义任意GPIO为SPI,也可以实现QSPI。

typedef struct 
{struct ftdi_context *ftdi;uint8_t sck;uint8_t mosi_io0;uint8_t miso_io1;uint8_t io2;uint8_t io3;uint8_t freq;spi_type_e type;spi_mode_e mode;bool msb;uint8_t *pCommand;int iCommand;
}mpsse_spi_s;

sck/mosi_io0/miso_io1/io2/io3分别对应SPI/QSPI的io,同样,所有io都必须属于同一组。注意,CS脚单独控制,用GPIO的方式控制。

type表示该SPI是哪种类型:

typedef enum
{SPI_TYPE_SPI = 0,SPI_TYPE_QSPI,
}spi_type_e;

mode则表示spi的模式

由于多个SPI从设备可以共用sck和数据口,只使用不同的cs即可。所以这里只定义2个spi设备

typedef enum
{SPI_PORT_0 = 0,SPI_PORT_1,SPI_PORT_MAX,
}mpsse_spi_port_e;mpsse_spi_s spi[SPI_PORT_MAX];

1. 初始化

void spiInit(uint8_t port, mpsse_spi_s init)
{if(port >= SPI_PORT_MAX)return;spi[port] = init;spi[port].freq += 1;
}

2. SCK默认电平设置

根据SPI的模式设置SCK默认电平,如果是模式0和模式1,SCK默认是低电平(CPOL = 0),如果是模式2和模式3(CPOL = 1),SCK默认是高电平。

void spiCPOL(uint8_t port)
{if(spi[port].mode == SPI_MODE0 || spi[port].mode == SPI_MODE1)mpsseGpioWrite(spi[port].sck, 0);elsempsseGpioWrite(spi[port].sck, 1);
}

这个函数要在CS设置为低前调用。

3. GPIO控制

将GPIO控制定义好宏

#define spiSCKHigh(port)        do{\gpio.level |= ((uint16_t)1 << spi[port].sck);\
}while(0)#define spiSCKLow(port)        do{\gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].sck));\
}while(0)#define spiMOSIHigh(port)       do{\gpio.level |= ((uint16_t)1 << spi[port].mosi_io0);\
}while(0)#define spiMOSILow(port)        do{\gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].mosi_io0));\
}while(0)#define spiMISORead(port)    do{\spi[port].pCommand[spi[port].iCommand++] = gpioReadCommand[gpioCommand(port)];\
}while(0)

4. spi全双工通信

spi类型下传输数据接口函数:

int spiTransferBytes(uint8_t port, uint8_t* wrBuf, uint8_t* rdBuf, uint16_t len)

这种模式下是全双工的模式,和选择的模式有关。CPHA表示选择第几个沿来

当CPHA = 0时,表示第一个沿采集数据(即读入数据);当CPHA = 1时,表示第二个沿采集数据。由于CPOL确定了SCK初始电平,所以这2个设置就决定了第一个是上升沿还是下降沿。

modeCPOLCPHA
mode 000
mode 101
mode 210
mode 311

mode 0: SCK初始电平为低电平, 在第一个上升沿前MOSI输出,然后在上升沿后MISO读入,完成第一个位的传输。

mode 1: SCK初始电平为低电平, 在第一个上升沿前MOSI输出,等到下降沿后MISO读入

mode 2: SCK初始电平为高电平, 在第一个下降沿前MOSI输出,然后在下降沿后MISO读入,完成第一个位的传输。

mode 3: SCK初始电平为高电平, 在第一个下降沿前MOSI输出,等到上升沿后MISO读入

    int size = len;int j = 0;while(size > 0){int i;uint8_t wrDat;if(wrBuf == NULL)wrDat = 0xff;elsewrDat = wrBuf[j];uint8_t level = 0; for(i = 0; i < 8; i++){if(spi[port].msb == false){level = (wrDat & 0x01) ? 1 : 0;wrDat >>= 1;}else{level = (wrDat & 0x80) ? 1 : 0;wrDat <<= 1;}switch(spi[port].mode){case SPI_MODE0:default:spi0TransferBit(port, level);break;case SPI_MODE1:spi1TransferBit(port, level);break;case SPI_MODE2:spi2TransferBit(port, level);break;case SPI_MODE3:spi3TransferBit(port, level);break;}}j++;size--;}

4.1 MSB/LSB

在发送前,先根据高位在前MSB还是低位在前LSB准备好要发送的位

        for(i = 0; i < 8; i++){if(spi[port].msb == false){level = (wrDat & 0x01) ? 1 : 0;wrDat >>= 1;}else{level = (wrDat & 0x80) ? 1 : 0;wrDat <<= 1;}switch(spi[port].mode)

4.2 分配command缓存

一个位写需要3 * 3 * freq个字节命令,最后需要加一个0x87命令,所以一共需要分配的字节数为:

    int commandlength = size * 3 * 3 * spi[port].freq + 1;spi[port].pCommand = (uint8_t *)malloc(commandlength);spi[port].iCommand = 0;

4.3 spi0TransferBit

模式0下传输一个位,在上升沿前MOSI输出位信息,然后产生上升沿,从设备准备好数据,主机读入数据,最后拉低SCK,完成一个位的传输。

void spi0TransferBit(uint8_t port, uint8_t level)
{if(level == 0)spiMOSILow(port);elsespiMOSIHigh(port);spiCommandWrite(port, 1);spiSCKHigh(port);spiCommandWrite(port, 1);spiMISORead(port);spiSCKLow(port);spiCommandWrite(port, 1);
}

4.3 spi1TransferBit

和模式0最大的区别是在第二个时钟沿读入MISO

void spi1TransferBit(uint8_t port, uint8_t level)
{if(level == 0)spiMOSILow(port);elsespiMOSIHigh(port);spiCommandWrite(port, 1);spiSCKHigh(port);spiCommandWrite(port, 1);spiSCKLow(port);spiCommandWrite(port, 1);spiMISORead(port);
}

4.4 spi2TransferBit

和模式0相比,sck方向反过来就是模式2.

void spi2TransferBit(uint8_t port, uint8_t level)
{if(level == 0)spiMOSILow(port);elsespiMOSIHigh(port);spiCommandWrite(port, 1);spiSCKLow(port);spiCommandWrite(port, 1);spiMISORead(port);spiSCKHigh(port);spiCommandWrite(port, 1);
}

4.5 spi3TransferBit

和模式1相比,sck方向反过来就是模式3

void spi3TransferBit(uint8_t port, uint8_t level)
{if(level == 0)spiMOSILow(port);elsespiMOSIHigh(port);spiCommandWrite(port, 1);spiSCKLow(port);spiCommandWrite(port, 1);spiSCKHigh(port);spiCommandWrite(port, 1);spiMISORead(port);
}

4.6 写命令序列

最后加一个0x87命令

spi[port].pCommand[spi[port].iCommand++] = 0x87;

同样,判断一下command缓存是否足够

    if(spi[port].iCommand > commandlength){printf("spi transfer error: command buffer is overflow %d:%d\n", spi[port].iCommand, commandlength);if(spi[port].pCommand)free(spi[port].pCommand);return -2;}

发送command数据

    int writetimeout = spi[port].ftdi->usb_write_timeout;spi[port].ftdi->usb_write_timeout = spi[port].iCommand * spi[port].freq;int ret = ftdi_write_data(spi[port].ftdi, spi[port].pCommand, spi[port].iCommand);if(spi[port].pCommand)free(spi[port].pCommand);spi[port].ftdi->usb_write_timeout = writetimeout;if(ret < 0){printf("usb write fail %d\n", ret);return -3;}

4.7 读数据

读入所有的位数据

    int rdLen = len * 8;uint8_t *pReadBuf = (uint8_t *)malloc(rdLen);int readtimeout = spi[port].ftdi->usb_read_timeout;spi[port].ftdi->usb_read_timeout = spi[port].iCommand + rdLen * spi[port].freq;ret = ftdi_read_data(spi[port].ftdi, pReadBuf, rdLen);printf("read data number:%d\n", ret);if(ret < rdLen){//try againprintf("retry read:%d\n", ret);int remain;if(ret < 0){remain = rdLen;ret = ftdi_read_data(spi[port].ftdi, pReadBuf, rdLen);} else{remain = rdLen - ret;ret = ftdi_read_data(spi[port].ftdi, pReadBuf + ret, remain);}if(ret + remain < rdLen){if(pReadBuf)free(pReadBuf);spi[port].ftdi->usb_read_timeout = readtimeout;return -3;}}spi[port].ftdi->usb_read_timeout = readtimeout;

4.8 组合实际数据

因为读入的数据是整个8位数据,所以将对应位的数据组合为8位数据。如果rdBuf是空指针,那么就不需要读数据,这一步就跳过。

    if(rdBuf != NULL){int j = 0;for(int i = 0; i < rdLen; i++){int max = i + 8;uint8_t tmp = 0;for(; i < max; i++){if(spi[port].msb == true){tmp <<= 1;if ((pReadBuf[i] & ((uint8_t)(1 << (spi[port].miso_io1 % 8)))) == (uint8_t)(1 << (spi[port].miso_io1 % 8))){tmp |= 0x01;}}else{tmp >>= 1;if ((pReadBuf[i] & ((uint8_t)(1 << (spi[port].miso_io1 % 8)))) == (uint8_t)(1 << (spi[port].miso_io1 % 8))){tmp |= 0x80;}}}rdBuf[j++] = tmp;i--;}if(pReadBuf)free(pReadBuf);}

最后释放缓存,返回即可。

    if(pReadBuf)free(pReadBuf);printf("SPI Transfer Bytes OK\n");

4. 9 读写函数

可以通过宏定义定义好SPI接口的读写函数

#define spiWriteBytes(port, wrBuf, len)     spiTransferBytes(port, wrBuf, NULL, len)
#define spiReadBytes(port, rdBuf, len)      spiTransferBytes(port, NULL, rdBuf, len)

5. 验证

SPI接口接SPI NOR Flash,其中ADBUS0接MOSI,ADBUS1接MISO,ADBUS2接SCK,ADBUS3接CS。

5.1 初始化FTDI设备模式

    int ret;ret = ftdi_set_bitmode(ftdi, 0, BITMODE_MPSSE);if(ret < 0){printf("Set Mode Fail: %d\n", ret);return EXIT_FAILURE;}

5.2 初始化GPIO

#define SFLASH_CS_PIN       3mpsse_gpio_s gpioSetting;gpioSetting.ftdi = ftdi;gpioSetting.dir = 0x0000; //All inputgpioSetting.dir |= ((uint16_t)GPIO_DIR_OUT << spiSetting.sck) | ((uint16_t)GPIO_DIR_OUT << spiSetting.mosi_io0)| ((uint16_t)GPIO_DIR_OUT << SFLASH_CS_PIN);        //CSgpioSetting.level = 0xFFFF;mpsseGpioInit(gpioSetting);

5.3 控制CS

直接使用gpio的api控制CS的高低电平。

#define SFLASH_CS_PIN       3
void sflash0CS(uint8_t port, bool enable)
{spiCPOL(port);mpsseGpioWrite(SFLASH_CS_PIN, !enable);
}

5.4 读flash的id

    #define CMD_READ_ID             0x9Fuint8_t cmd[4] = {CMD_READ_ID, 0xff, 0xff, 0xff};uint32_t id;sflash[port].pCSEnable(port, true);spiWriteBytes(sflash[port].spiPort, cmd, 1);spiReadBytes(sflash[port].spiPort, cmd + 1, 3);sflash[port].pCSEnable(port, false);id = ((uint32_t)cmd[1] << 16) | (uint32_t)(cmd[2] << 8) | (uint32_t)(cmd[3]);printf("sflash id is:%x\n", id);

5.5 结果

sflash id is:ef4018

这个波形如下图: 

 而最大频率大概为450KHz,有点低

如果需要更高频率,应该要采用官方的方式,固定SCK,MISO和MOSI的方式。

还有一种优化方式,就是将所有的命令一次发出,例如pCSEnable + spiWriteBytes + spiReadBytes + pCSEnable这4个的IO控制序列组合起来,一次写给设备。例如:

    sflash[port].pCSEnable(port, true);//spiWriteBytes(sflash[port].spiPort, cmd, 1);//spiReadBytes(sflash[port].spiPort, cmd + 1, 3);spiTransferBytes(sflash[port].spiPort, cmd, cmd, 4);sflash[port].pCSEnable(port, false);

它的波形就变为:

这篇关于libftdi1学习笔记 4 - MPSSE SPI的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/909930

相关文章

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个