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);

它的波形就变为:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/820905.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

短视频底层逻辑分析

短视频底层逻辑 1.迭代模型_ev 2.Douyin的本质_ev 3.Douyin的审核机制_ev 4.平台趋势_ev 5.定位_ev 6.建立用户期待_ev 7.好内容的定义_ev 8怎么做好内容_ev 9.如何做好选题_ev 10.如何快速模仿_ev 11.账号拆解的底层逻辑_ev 12选人的重要性_ev 13.内容的包装_ev 14.打造大IP的…

【中间件】ElasticSearch简介和基本操作

一、简介 Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎&#xff0c;支持各种数据类型&#xff0c;包括文本、数字、地理、结构化、非结构化 ,可以让你存储所有类型的数据&#xff0c;能够解决不断涌现出的各种用例。其构成如下&#xff1a; 说明&#xff1…

Python数学建模学习-PageRank算法

1-基本概念 PageRank算法是由Google创始人Larry Page在斯坦福大学时提出&#xff0c;又称PR&#xff0c;佩奇排名。主要针对网页进行排名&#xff0c;计算网站的重要性&#xff0c;优化搜索引擎的搜索结果。PR值是表示其重要性的因子。 中心思想&#xff1a; 数量假设&#…

创领“浆”来:三一重工特种砂浆站引领行业绿色高效新纪元

随着我国城市化进程不断加快&#xff0c;国家对城市建设高度关注&#xff0c;国务院办公厅针对城镇老旧小区基础设施匮乏、住房条件落后、环境污染严重等突出问题印发了《关于全面推进城镇老旧小区改造工作的指导意见》&#xff0c;各地积极响应&#xff0c;因此特种砂浆也迎来…

【微信取证篇】微信收藏图片存储记录思维导图

【微信取证篇】微信收藏图片存储记录思维导图 最近在测试微信收发图片和收藏的功能&#xff0c;发现许多有意思的地方&#xff0c;**微信收藏图片缓存的文件目前发现有三个地方&#xff0c;都是在Fav下&#xff0c;名称都一样&#xff0c;有直接原始图片&#xff0c;也有加密的…

2024年nodejs调用小红书最新关注(粉丝)follow接口,api接口分析2004-04-16

一、打开chrome按f12&#xff0c;点击右上角的“关注”按钮&#xff0c;抓包位置如下&#xff1a; (图1 follow接口) 二、follow接口分析 1、请求地址 https://edith.xiaohongshu.com/api/sns/web/v1/user/follow 2、请求方法: POST 3、请求头&#xff1a; :authority: edith…

telnet不是内部或外部命令也不是可运行的程序或批处理文件

出现问题 在Windows命令行运行telnet命令&#xff0c;出现&#xff1a; ‘telnet’ 不是内部或外部命令&#xff0c;也不是可运行的程序&#xff0c;或批处理文件。 问题原因 Windows系统中的telnet客户端是关闭状态。因为Win10默认没有安装telnet功能。 解决方式 将系统中…

FPGA - 以太网UDP通信(一)

一&#xff0c;简述以太网 以太网简介 ​以太网是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以太网的技术标准&#xff0c;它规定了包括物理层的连线、电子信号和介质访问层协议的内容。 ​ 以太网类型介绍 以太网是现实世界中最普遍的一种计算机网络。以太网有…

[C++][算法基础]二分图的最大匹配(匈牙利算法)

给定一个二分图&#xff0c;其中左半部包含 n1 个点&#xff08;编号 1∼n1&#xff09;&#xff0c;右半部包含 n2 个点&#xff08;编号 1∼n2&#xff09;&#xff0c;二分图共包含 m 条边。 数据保证任意一条边的两个端点都不可能在同一部分中。 请你求出二分图的最大匹配…

Intel显卡驱动导致Qt opengl 渲染YUV时拉伸窗口内存泄漏

最近在使用QOpenGLWidget做YUV视频渲染&#xff0c;发现在拉伸窗口的时候内存暴涨&#xff0c;如果窗口不动则内存不变。 可以得出结论一定是resizeGL出了问题&#xff0c;但是其实这里代码很简单 glViewport(0, 0, w, h); 还有就是变换矩阵计算&#xff0c;根本没资源建立与释…

Sublime Text下载,安装,安装插件管理器,下载汉化插件

SublimeTest官网 © Sublime Text中文网 下载安装 一路点击安装即可 安装插件管理器 管理器官网安装 - 包控制 (packagecontrol.io) 手动安装将3 位置点击网址下载 再打开SublimeTest 点击 选择第一个Browse Packages..... 将会跳转到文件夹中 进入上一个文件夹 在进入…

【读点论文】YOLOX: Exceeding YOLO Series in 2021,无锚框单阶段目标检测方案,解耦检测头的分类和回归分支,优化标签分配策略

YOLOX: Exceeding YOLO Series in 2021 Abstract 在本报告中&#xff0c;我们介绍了YOLO系列的一些经验改进&#xff0c;形成了一种新的高性能探测器—YOLOX。我们将YOLO检测器切换到无锚方式&#xff0c;并进行其他先进的检测技术&#xff0c;即去耦头和领先的标签分配策略S…

4月16号总结

java学习 网络编程 1.网络分层 网络分层是将网络通信划分为不同的逻辑层次&#xff0c;每一层负责特定的功能&#xff0c;从而实现网络通信的模块化和标准化。常用的网络分层模型包括OSI&#xff08;开放系统互联&#xff09;模型和TCP/IP模型。 特点和作用&#xff1a; 分…

origin绘图操作合集

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、图例去掉边框二、柱状图单独选中某一柱子修改颜色&#xff0c;柱状图中设置一个柱子的方法 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参…

Java开发从入门到精通(二十):Java的面向对象编程OOP:IO流中的转换流、打印流、数据流、序列流、IO框架

Java大数据开发和安全开发 &#xff08;一&#xff09;Java的IO流文件读写1.1 转换流1.1.1 InputStreamReader字符输入转换流1.1.1 OutputStreamWriter字符输出转换流 1.2 打印流1.2.1 PrintStream打印流1.2.2 PrintWriter打印流1.2.3 PrintStream和PrintWriter的区别1.2.4 打印…

获取淘宝京东商品详情API接口返回数据解析说明(可测试,批量获取)

获取淘宝和京东的商品详情API接口返回数据并解析通常需要遵循以下几个步骤&#xff1a; 淘宝商品详情API 淘宝的API接口通常对商家和合作伙伴开放&#xff0c;并且需要经过严格的申请和审核流程。普通用户或未经授权的开发者通常无法直接访问淘宝的商品详情API。 如果你已经…

Innodb之redo日志

Innodb引擎执行流程 redo log ​ MySQL中的redo log&#xff08;重做日志&#xff09;是实现WAL&#xff08;预写式日志&#xff09;技术的关键组件&#xff0c;用于确保事务的持久性和数据库的crash-safe能力。借用《孔乙己》中酒店掌柜使用粉板记录赊账的故事&#xff0c;…

2024.4.16

三个按键的中断 do_irq.c #include "mykey.h" extern void printf(const char *fmt, ...); unsigned int i 0; void do_irq(void) {//获取中断号unsigned int irqno (GICC->IAR&0x3ff);switch (irqno){case 99://中断处理逻辑printf("KEY1_INTC\n&q…

jenkins构建微信小程序并展示二维码

测试小程序的过程中&#xff0c;很多都是在回头和前端开发说一句&#xff0c;兄弟帮我打一个测试版本的测试码&#xff0c;开发有时间的情况下还好&#xff0c;就直接协助了&#xff0c;但是很多时候他们只修复了其中几个bug&#xff0c;其他需要修复的bug代码正在编写&#xf…