半导体行业的芯片设计流程及代表企业详解
芯片一般是指集成电路的载体,也是集成电路经过设计、制造、封装、测试后的结果,通常是一个可以立即使用的独立的整体。 一颗小小的芯片,却包含了几千万个晶体管。芯片是电子设备中最重要的部分,承担着运算和存储的功能。
我们知道建造楼房是需要施工设计图的,有了施工设计图,工人们才知道如何去操作。因此,施工设计图的重要性不言而喻。
有人把芯片制造比喻为建造楼房,芯片设计就是那张施工设计图。但不同的是,相比那张施工设计图,芯片设计更为复杂,下面这张图就可以说明。
芯片设计流程
要想造个芯片,首先,你得画出来一个长这样的玩意儿给Foundry (外包的晶圆制造公司),如下图:
放大一点:
再放大一点,我们可以看到是一个门电路! 这是一个NAND Gate(与非门) :
那上面这个玩意画出来,究竟有哪些步骤?设计流程可以简单分成如下:
1
第一步,制定规格
在IC设计中,最重要的步骤就是规格制定。 这个步骤就像是在设计建筑前,先决定要几间房间、浴室,有什么建筑法规需要遵守,在确定好所有的功能之后在进行设计,这样才不用再花额外的时间进行后续修改。IC 设计也需要经过类似的步骤,才能确保设计出来的芯片不会有任何差错。
规格制定的第一步便是确定 IC 的目的、效能为何,对大方向做设定。接着是察看有哪些协定要符合,像无线网卡的芯片就需要符合 IEEE 802.11 等规范,不然,这芯片将无法和市面上的产品相容,使它无法和其他设备连线。最后则是确立这颗 IC 的实作方法,将不同功能分配成不同的单元,并确立不同单元间连结的方法,如此便完成规格的制定。
2
第二步,设计芯片细节
制定完规格后,接着就是设计芯片的细节了。 这个步骤就像初步记下建筑的规画,将整体轮廓描绘出来,方便后续制图。在 IC 芯片中,便是使用硬体描述语言(HDL)将电路描写出来。常使用的 HDL 有 Verilog、VHDL 等,藉由程式码便可轻易地将一颗 IC 地功能表达出来。接着就是检查程式功能的正确性并持续修改,直到它满足期望的功能为止。
32 bits 加法器的 Verilog 范例
3
第三步,画出平面设计蓝图
再完整规画后,接下来便是画出平面的设计蓝图。 在 IC 设计中,逻辑合成这个步骤便是将确定无误的 HDL code,放入电子设计自动化工具(EDA tool),让电脑将 HDL code 转换成逻辑电路,产生如下的电路图。之后,反覆的确定此逻辑闸设计图是否符合规格并修改,直到功能正确为止。
控制单元合成后的结果
4
第四步,电路布局与布线
将合成完的程式码再放入另一套 EDA tool,进行电路布局与布线(Place And Route)。 在经过不断的检测后,便会形成如下的电路图。图中可以看到蓝、红、绿、黄等不同颜色,每种不同的颜色就代表着一张光罩。
常用的演算芯片- FFT 芯片,完成电路布局与绕线的结果
5
第五步,层层光罩,叠起一颗芯片
目前已经知道一颗 IC 会产生多张的光罩,这些光罩有上下层的分别,每层有各自的任务。下图为简单的光罩例子,以积体电路中最基本的元件 CMOS 为范例,CMOS 全名为互补式金属氧化物半导体(Complementary metal–oxide–semiconductor),也就是将 NMOS 和 PMOS 两者做结合,形成 CMOS。至于什么是金属氧化物半导体(MOS)?这种在芯片中广泛使用的元件比较难说明,一般读者也较难弄清,在这里就不多加细究。
上图中,左边就是经过电路布局与绕线后形成的电路图,在前面已经知道每种颜色便代表一张光罩。右边则是将每张光罩摊开的样子。制作时,便由底层开始,依循上一篇 IC 芯片的制造中所提的方法,逐层制作,最后便会产生期望的芯片了。
整体来看,芯片设计是一门非常复杂的专业。多亏电脑辅助软体的成熟,才让 IC 设计得以加速。IC 设计厂十分依赖工程师的智慧,上面所述的每个步骤都有其专门的知识,皆可独立成多门专业的课程,像是撰写硬体描述语言就不仅仅需要熟悉程式语言,还需要了解逻辑电路是如何运作、如何将所需的演算法转换成程式、合成软体是如何将程式转换成逻辑闸等问题。
国内芯片设计行业现状
目前,国内芯片设计企业数量趋于稳定,截止2017年10月底,全国共有约1380家设计企业,比去年的1362家多了18家,总体变化不大。
2009-2017 年中国IC 设计公司数目(2017 为2017 年10 月)
数据来源:中国产业信息 制表:芯师爷
从设计业销售变化情况来看, 2017年全行业销售预计为1945.98亿元,比2016年的1518.52亿元增长28.15%,在全球集成电路设计业所占的比重再次大幅提高。
2014-2016 年中国IC 设计公司营收情况(2017 为2017 年10 月)
数据来源:中国产业信息 制表:芯师爷
2017年我国芯片设计业的从业人员规模与2016年的基本相同,略有增长,大约为14万人。
2016-2017 年中国不同方向IC 设计的收入规模(单位:亿元)
数据来源:中国产业信息 制表:芯师爷
从事通信芯片设计的企业从去年的241家增加到266家,销售额增长了30.7%,达到899.74亿元。
从事多媒体芯片设计的企业从去年的43家到增长72家,销售总额下降了0.63%,达到175.57亿元;
从事模拟芯片设计的企业数量从219家减少到180家,销售额增长了5.14%,达到68亿元。
从事功率器件设计的企业从77家增加到82家,销售额增长了155.14%,达到76.67亿元,为2017年增长最快的产品领域;
从事消费类电子芯片设计的企业数量从上年的589家增加到610家,销售增长45.2%,达452.33亿元,继续保持了2016年的快速增长势头。
从事智能卡芯片设计企业从上年的69家减少到62家,但销售总和上升了5.68%,达到139.15亿元。
从事计算机芯片设计的企业数量从去年的107家减少到85家,但销售提升了13.99%,达到128.28亿元。
2017年各类芯片设计企业数量及销售额
数据来源:中国产业信息 制表:芯师爷
2004年全球IC设计公司前50名中没有一家中国本土IC设计公司,2015 年海思半导体已经进入前十名,2016年海思与紫光展锐进入全球前十。此外北京豪威、中兴微电子、华大半导体、智芯微、汇顶科技等也进入前50,足以说明大陆本土IC设计公司的实力以及发展速度。
中国大陆十大芯片设计厂商
据集邦咨询最新研究报告指出,2017年中国IC设计业产值预估达人民币2006亿元,而2016年中国IC设计业的销售额为1644.3亿元,年增率为22%,预估2018年产值有望突破人民币2400亿元,维持约20%的年增速。
根据集邦咨询预估的2017年IC设计产业产值与厂商营收排名,今年前十大IC设计厂商排名相较于2016年的状况略有调整,大唐半导体设计将无缘前十,兆易创新和韦尔半导体凭借优异的营收表现进入营收排行前十名。
数据来源:集邦咨询 制表:芯师爷
备注:1)排名仅统计公司总部位于中国大陆的企业
2)豪威、ISSI等被中资收购,但截止数据统计前(2017/12)还未交由中古企业经营
1.海思半导体
官网: http://www.hisilicon.com/
公司简介: 海思半导体有限公司成立于2004年10月,前身是创建于1991年的华为集成电路设计中心。 海思公司总部位于深圳,在北京、上海、美国硅谷和瑞典设有设计分部。
海思的产品覆盖无线网络、固定网络、数字媒体等领域的芯片及解决方案,成功应用在全球100多个国家和地区;在数字媒体领域,已推出SoC网络监控芯片及解决方案、可视电话芯片及解决方案、DVB芯片及解决方案和IPTV芯片及解决方案。
主要产品: 麒麟980、麒麟970、麒麟960、麒麟950、麒麟935、麒麟930、麒麟928、麒麟925、麒麟920、麒麟910T、Hi3789MK3V2
2.紫光展锐
官网: http://spreadtrum.com/en/index.html
公司简介: 紫光集团于2013年收购展讯通信,2014年收购锐迪科,并于2016年将两者整合为紫光展锐。作为紫光集成电路产业链中的核心企业,紫光展锐致力于移动通信和物联网领域核心芯片的自主研发及设计,产品涵盖2G/3G/4G/5G移动通信基带芯片、物联网芯片、射频芯片、无线连接芯片、安全芯片、电视芯片等。
目前,紫光展锐的员工数量超过4500人,在全球拥有14个技术研发中心,8个客户支持中心,致力成为全球前三的手机基带芯片设计公司、中国最大的泛芯片供应商和中国领先的5G通信芯片企业,并通过自主创新和国际合作的双轮驱动,稳步成为世界数一数二的芯片设计企业。
主要产品: 移动通讯芯片“虎贲”、泛连接芯片“春藤”
3.中兴微电子
官网: https://www.zte.com.cn/china/
公司简介: 深圳市中兴微电子技术有限公司(以下简称“中兴微电子”)于2003年注册成立。中兴微电子以通信技术为核心,致力于成为全球领先的综合芯片供应商。中兴微电子前身是成立于1996 年的中兴通讯 IC 设计部, 主要从事有线、无线芯片的设计和开发工作,基带芯片从 3G、 WiMax、 LTE 等所有制式都是自主研发。
中兴微电子掌握国际一流的IC设计与验证技术,拥有先进的EDA设计平台、COT设计服务、开发流程和规范,自研芯片研发并成功商用的有100多种,覆盖通讯网络、个人应用、智能家庭和行业应用等“云管端”全部领域,在国内处于行业前列。
主要产品: NB-IoT芯片朱雀RoseFinch 7100
4.华大半导体
官网: http://www.hdsc.com.cn/
公司简介: 华大半导体有限公司(简称华大半导体)是中国电子信息产业集团有限公司(CEC)整合旗下集成电路企业而组建的专业子集团。旗下有三家上市公司,总资产规模超过100亿。2014年5月8日在上海成立,专业从事集成电路设计及相关解决方案。
2017年位列国内集成电路设计企业第四名,在智能卡及安全芯片、智能卡应用、模拟电路、新型显示等领域占有较大的份额。
主要产品: 新型显示、模拟电路、RFID、MCU、智能卡
5.汇顶科技
官网: https://www.goodix.com/
公司简介: 汇顶科技是一家基于芯片设计和软件开发的整体应用解决方案提供商,目前主要面向智能移动终端市场提供领先的人机交互和生物识别解决方案,并已成为安卓阵营全球指纹识别方案第一供应商。
产品和解决方案主要应用于华为、OPPO、vivo、小米、中兴、魅族、锤子、Amazon、Samsung、Nokia、Dell、HP、LG、ASUS、acer、TOSHIBA、Panasonic等国际国内知名品牌。
主要产品: 屏下光学指纹识别技术、活体指纹识别方案、IFS、盖板指纹识别方案、Coating指纹识别方案、Goodix Link、AMOLED
6.智芯微
官网: http://www.sgcc.com.cn/html/sgcc_main/index.shtml
公司简介: 北京智芯微电子科技有限公司(简称智芯微公司)成立于2010年,是国家电网公司所属芯片设计企业,注册资本50亿元。拥有广州分公司、深圳市国电科技通信有限公司(简称深国电)、国网思极紫光(青岛)微电子科技有限公司等3家分子公司。
智芯微公司打造基于顶层自主芯片引领和支撑,以安全传感、终端通信、能效控制为依托的业务发展模式,建设成为以智能芯片为核心的整体解决方案提供商。已开发“安全、主控、通信、传感、射频识别(RFID)”五大类 67 款芯片产品,累计销售各类芯片 8亿 颗。
主要产品: 通信芯片、射频芯片、传感器
7.士兰微
官网: http://www.silan.com.cn/all/default.aspx
公司简介: 杭州士兰微电子股份有限公司(上交所股票代码:士兰微,600460)坐落于杭州高新技术产业开发区,成立于1997年。士兰微是专业从事集成电路芯片设计以及半导体微电子相关产品生产的高新技术企业,公司现在的主要产品是集成电路和半导体产品。
主要产品: 半导体分立器件成品、半导体分立器件芯片、计量类电路、MEMS传感器、消费类专用电路、数字音视频(含CD音响)电路、音响系统电路、功率驱动模块、LED照明驱动电路、电源管理电路、MCU电路
8.韦尔半导体
官网: http://www.willsemi.com/
公司简介: 海韦尔半导体有限公司是一家以自主研发、销售服务为主体的半导体器件设计和销售公司,公司成立于2007年5月,总部坐落于有“中国硅谷”之称的上海张江高科技园区,在深圳、台湾、香港等地设立办事处。
公司主营产品包括保护器件 (TVS、TSS)、功率器件 (MOSFET、Schottky Diode、Transistor)、电源管理器件 (Charger、LDO、Buck、Boost、Backlight LED Driver、Flash LED Driver)、模拟开关等四条产品线,700多个产品型号,产品在手机、电脑、电视、通讯、安防、车载、穿戴、医疗等领域得到广泛应用。
主要产品: 保护器件、肖特基二极管、场效应晶体管、双极性晶体管、电源管理、模拟开关、USB-C接口电路、射频器件、音频器件
9.中星微电子
官网: http://www.vimicro.com.cn/introduction.htm
公司简介: 1999年,在国家工业和信息化部(原信息产业部)的直接领导下,在发改委、财政部、科技部、商务部、北京市人民政府和中关村管委会等有关部门的大力支持下,由多位来自硅谷的博士企业家在北京中关村科技园区创建了中星微电子有限公司,启动并承担了国家战略项目——“星光中国芯工程”,致力于数字多媒体芯片的开发、设计和产业化。
2005年,中星微电子在美国纳斯达克证券市场成功上市,成为第一家在纳斯达克上市的具有自主知识产权的中国芯片设计企业。此外, 中星微电子还是中国“数字多媒体芯片技术国家重点实验室”的依托单位,并承担了国家发改委、信息产业部、科技部、商务部等多项重大项目。
主要产品: SVAC视频安全摄像头芯片、人工智能SVAC视频安全头芯片、神经网络处理器、H.264解压缩芯片、高清图像采集模块、PC摄像头芯片
10.兆易创新
官网: http://www.gigadevice.com/
公司简介: 北京兆易创新科技股份有限公司(简称“兆易创新”,沪市股票代码603986),成立于2005年4月,是一家以中国为总部的全球化芯片设计公司。
公司致力于各类存储器(SPI NOR FLASH®、SPI NAND FLASHTM)、控制器(GD32TM MCU)以及eMMC的设计研发,研发人员比例占全员比例60%以上,在中国大陆(北京/合肥/西安/上海/深圳)、香港、台湾、韩国、美国、日本、英国等多个国家和地区设有分支机构,营销网络遍布全球,并与多家世界知名晶圆厂、封装测试厂结成战略合作伙伴关系,为客户提供优质便捷的本地化支持服务。
主要产品: NOR Flash、SPI NAND Flash、GD32®Cortex®-M MCU
【驱动】SPI驱动分析(七)-SPI驱动常用调试方法
## 用户态
用户应用层使用spidev驱动的步骤如下:
1. 打开SPI设备文件:用户可以通过打开`/dev/spidevX.Y`文件来访问SPI设备,其中X是SPI控制器的编号,Y是SPI设备的编号。
2. 配置SPI参数:用户可以使用ioctl命令`SPI_IOC_WR_MODE`、`SPI_IOC_WR_BITS_PER_WORD`和`SPI_IOC_WR_MAX_SPEED_HZ`来设置SPI模式、数据位数和时钟速度等参数。
3. 发送和接收数据:用户可以使用read和write系统调用来发送和接收SPI数据。写入的数据将被传输到SPI设备,而从设备读取的数据将被存储在用户提供的缓冲区中。
4. 关闭SPI设备文件:当不再需要与SPI设备通信时,用户应该关闭SPI设备文件。
总结起来,spidev驱动提供了一种简单而灵活的方式来与SPI设备进行通信,使得用户可以轻松地在Linux系统上开发和控制SPI设备。
`spidev`驱动有现成的测试工具。其中一个常用的测试工具是`spi_test`,它是`spidev`驱动自带的测试工具,可以用于测试和调试SPI设备。`spi_test`可以通过命令行参数设置SPI设备的各种参数,如设备文件、传输速率、字节顺序等。使用spi_test可以发送和接收SPI数据,以验证spidev驱动的功能和性能。
在源码`Documentation\spi`路径下,有两个测试工具的源码文件,`spidev_fdx.c`和`spidev_test.c`文件。可以直接交叉编译为可执行文件使用。这些工具都基于`spidev`通用设备驱动以及对应的ioctl命令实现,可以方便的用来对spi的通用型驱动来进行测试。
`parse_opts`这段代码通过解析命令行选项,并根据选项的值设置相应的变量,实现了对命令行参数的解析和处理。
```c
static void parse_opts(int argc, char *argv[])
{
while (1) {
static const struct option lopts[] = {
{ "device", 1, 0, 'D' },
{ "speed", 1, 0, 's' },
{ "delay", 1, 0, 'd' },
{ "bpw", 1, 0, 'b' },
{ "loop", 0, 0, 'l' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "lsb", 0, 0, 'L' },
{ "cs-high", 0, 0, 'C' },
{ "3wire", 0, 0, '3' },
{ "no-cs", 0, 0, 'N' },
{ "ready", 0, 0, 'R' },
{ "dual", 0, 0, '2' },
{ "verbose", 0, 0, 'v' },
{ "quad", 0, 0, '4' },
{ NULL, 0, 0, 0 },
};
int c;
c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR24p:v", lopts, NULL);
if (c == -1)
break;
switch (c) {
case 'D':
device = optarg;
break;
case 's':
speed = atoi(optarg);
break;
case 'd':
delay = atoi(optarg);
break;
case 'b':
bits = atoi(optarg);
break;
case 'l':
mode |= SPI_LOOP;
break;
case 'H':
mode |= SPI_CPHA;
break;
case 'O':
mode |= SPI_CPOL;
break;
case 'L':
mode |= SPI_LSB_FIRST;
break;
case 'C':
mode |= SPI_CS_HIGH;
break;
case '3':
mode |= SPI_3WIRE;
break;
case 'N':
mode |= SPI_NO_CS;
break;
case 'v':
verbose = 1;
break;
case 'R':
mode |= SPI_READY;
break;
case 'p':
input_tx = optarg;
break;
case '2':
mode |= SPI_TX_DUAL;
break;
case '4':
mode |= SPI_TX_QUAD;
break;
default:
print_usage(argv[0]);
break;
}
}
if (mode & SPI_LOOP) {
if (mode & SPI_TX_DUAL)
mode |= SPI_RX_DUAL;
if (mode & SPI_TX_QUAD)
mode |= SPI_RX_QUAD;
}
}
```
1. 声明一个静态的选项数组`lopts`,用于定义可接受的命令行选项。
2. 在循环内部,调用`getopt_long`函数来解析下一个选项。`getopt_long`函数会返回选项的短选项字符(c),如果没有更多选项则返回-1。
3. 使用switch语句根据选项的短选项字符进行分支处理。
4. 根据不同的选项,执行相应的操作。例如,对于选项'D',将其参数值赋给`device`变量;对于选项's',将其参数值转换为整数并赋给`speed`变量。
5. 如果遇到未知的选项,调用`print_usage`函数打印用法信息。
6. 循环结束后,根据设置的选项进行一些额外的逻辑处理。例如,如果设置了`SPI_LOOP`选项,则根据是否设置了`SPI_TX_DUAL`和`SPI_TX_QUAD`选项,设置相应的`SPI_RX_DUAL`和`SPI_RX_QUAD`选项。
`print_usage`打印spi_test的 使用方法。
```c
static void print_usage(const char *prog)
{
printf("Usage: %s [-DsbdlHOLC3]\n", prog);
puts(" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
" -d --delay delay (usec)\n"
" -b --bpw bits per word \n"
" -l --loop loopback\n"
" -H --cpha clock phase\n"
" -O --cpol clock polarity\n"
" -L --lsb least significant bit first\n"
" -C --cs-high chip select active high\n"
" -3 --3wire SI/SO signals shared\n"
" -v --verbose Verbose (show tx buffer)\n"
" -p Send data (e.g. \"1234\\xde\\xad\")\n"
" -N --no-cs no chip select\n"
" -R --ready slave pulls low to pause\n"
" -2 --dual dual transfer\n"
" -4 --quad quad transfer\n");
exit(1);
}
```
- `-D, --device <device>`:设置要使用的SPI设备,默认为`/dev/spidev1.0`。
- `-s, --speed <speed>`:设置SPI时钟速度,单位为Hz。
- `-d, --delay <delay>`:设置SPI传输之间的延迟时间,单位为微秒。
- `-b, --bits <bits>`:设置每个字的位数。
- `-l, --loop`:启用回环模式,将接收到的数据回送给发送方。
- `-H, --cpha`:将时钟相位设置为第二个边沿。
- `-O, --cpol`:将时钟极性设置为低电平活动。
- `-L, --lsb`:设置最低有效位(LSB)为先传输。
- `-C, --cs-high`:设置片选信号为高电平有效。
- `-3, --3wire`:设置3线SPI模式(共享SI/SO信号)。
- `-N, --no-cs`:禁用片选信号。
- `-v, --verbose`:启用详细输出模式,显示传输缓冲区的内容。
- `-t, --transfer <data>`:执行一个SPI传输,发送给定的数据字节。
- `-r, --read <count>`:执行一个SPI读传输,读取指定数量的字节。
- `-w, --write <data>`:执行一个SPI写传输,发送给定的数据字节。
- `-f, --file <file>`:从文件中读取数据并执行SPI传输。
- `-h, --help`:显示帮助信息。
`transfer`通过`ioctl`系统调用执行SPI数据传输操作。根据传入的参数和全局变量的设置,配置SPI传输的参数,并将发送和接收的数据进行打印。
```c
static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
int ret;
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = len,
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
};
if (mode & SPI_TX_QUAD)
tr.tx_nbits = 4;
else if (mode & SPI_TX_DUAL)
tr.tx_nbits = 2;
if (mode & SPI_RX_QUAD)
tr.rx_nbits = 4;
else if (mode & SPI_RX_DUAL)
tr.rx_nbits = 2;
if (!(mode & SPI_LOOP)) {
if (mode & (SPI_TX_QUAD | SPI_TX_DUAL))
tr.rx_buf = 0;
else if (mode & (SPI_RX_QUAD | SPI_RX_DUAL))
tr.tx_buf = 0;
}
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1)
pabort("can't send spi message");
if (verbose)
hex_dump(tx, len, 32, "TX");
hex_dump(rx, len, 32, "RX");
}
```
1.
2. 声明一个`spi_ioc_transfer`结构体变量`tr`,用于设置SPI传输的参数。
3. 在`spi_ioc_transfer`结构体中设置以下字段:
- `tx_buf`:指向发送数据缓冲区的指针。
- `rx_buf`:指向接收数据缓冲区的指针。
- `len`:要传输的数据长度。
- `delay_usecs`:传输之间的延迟时间(以微秒为单位)。
- `speed_hz`:SPI时钟速度(以赫兹为单位)。
- `bits_per_word`:每个字的位数。
4. 根据变量`mode`的值设置`tr`结构体中的`tx_nbits`和`rx_nbits`字段。如果`mode`中包含`SPI_TX_QUAD`标志,则将`tx_nbits`设置为4;如果`mode`中包含`SPI_TX_DUAL`标志,则将`tx_nbits`设置为2。类似地,如果`mode`中包含`SPI_RX_QUAD`标志,则将`rx_nbits`设置为4;如果`mode`中包含`SPI_RX_DUAL`标志,则将`rx_nbits`设置为2。
5. 如果`mode`中不包含`SPI_LOOP`标志,则根据`mode`中的其他标志设置`tr`结构体中的`tx_buf`和`rx_buf`字段。如果`mode`中包含`SPI_TX_QUAD`或`SPI_TX_DUAL`标志,则将`rx_buf`设置为0,表示在非回环模式下不接收数据。类似地,如果`mode`中包含`SPI_RX_QUAD`或`SPI_RX_DUAL`标志,则将`tx_buf`设置为0,表示在非回环模式下不发送数据。
6. 使用`ioctl`系统调用发送SPI消息并执行SPI数据传输操作。`SPI_IOC_MESSAGE(1)`表示发送单个SPI消息。
7. 检查`ioctl`的返回值`ret`,如果小于1,则表示SPI消息发送失败,调用`pabort`函数打印错误消息并终止程序。
8. 如果`verbose`标志为真,则使用`hex_dump`函数打印发送和接收数据的十六进制表示。
这段代码用于将输入字符串中的转义序列`\x`转换为对应的字符,并将结果存储在目标字符串中。它通过遍历输入字符串的字符,并根据转义序列的位置和格式进行解析和转换。
```c
static int unescape(char *_dst, char *_src, size_t len)
{
int ret = 0;
char *src = _src;
char *dst = _dst;
unsigned int ch;
while (*src) {
if (*src == '\\' && *(src+1) == 'x') {
sscanf(src + 2, "%2x", &ch);
src += 4;
*dst++ = (unsigned char)ch;
} else {
*dst++ = *src++;
}
ret++;
}
return ret;
}
```
`main`函数通过设置SPI设备的参数并执行数据传输操作与SPI设备进行通信。具体的数据传输操作在`transfer`函数中实现。
```c
int main(int argc, char *argv[])
{
int ret = 0;
int fd;
uint8_t *tx;
uint8_t *rx;
int size;
parse_opts(argc, argv);
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
/*
* spi mode
*/
ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);
if (ret == -1)
pabort("can't set spi mode");
ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);
if (ret == -1)
pabort("can't get spi mode");
/*
* bits per word
*/
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't set bits per word");
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't get bits per word");
/*
* max speed hz
*/
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't set max speed hz");
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't get max speed hz");
printf("spi mode: 0x%x\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
if (input_tx) {
size = strlen(input_tx+1);
tx = malloc(size);
rx = malloc(size);
size = unescape((char *)tx, input_tx, size);
transfer(fd, tx, rx, size);
free(rx);
free(tx);
} else {
transfer(fd, default_tx, default_rx, sizeof(default_tx));
}
close(fd);
return ret;
}
```
1. 调用`parse_opts`函数,解析命令行参数并设置全局变量。
2. 使用`open`函数打开SPI设备,以可读写方式打开。如果返回值小于0,则打印错误消息并终止程序。
3. 使用`ioctl`系统调用设置SPI设备的模式(`SPI_IOC_WR_MODE32`和`SPI_IOC_RD_MODE32`)、每字位数(`SPI_IOC_WR_BITS_PER_WORD`和`SPI_IOC_RD_BITS_PER_WORD`)以及最大时钟速度(`SPI_IOC_WR_MAX_SPEED_HZ`和`SPI_IOC_RD_MAX_SPEED_HZ`)。如果返回值为-1,则打印错误消息并终止程序。
4. 使用`printf`函数打印设置的SPI设备参数:模式、每字位数和最大时钟速度。
5. 如果`input_tx`不为NULL,则表示存在输入的发送数据。
- 计算输入发送数据的大小(排除末尾的`\0`)。
- 分配相应大小的内存给发送和接收缓冲区。
- 调用`unescape`函数,将输入发送数据中的转义序列反转义,并返回处理的字符数量。
- 调用`transfer`函数,执行SPI数据传输操作,将反转义后的发送数据发送到SPI设备,并接收数据到接收缓冲区。
- 释放发送和接收缓冲区的内存。
6. 否则,表示使用默认的发送和接收数据进行传输。
- 调用`transfer`函数,执行SPI数据传输操作,将默认的发送数据发送到SPI设备,并接收数据到接收缓冲区。
### spidev的缺点
使用read、write函数时,只能读、写,之二十半双工方式 使用ioctl可以达到全双工的读写 但是spidev有2个缺点:
- 不支持中断
- 只支持同步操作,不支持异步操作:就是read/write/ioctl这些函数只能执行完毕才可返回
完成代码如下
```c
/*
* SPI testing utility (using spidev driver)
*
* Copyright (c) 2007 MontaVista Software, Inc.
* Copyright (c) 2007 Anton Vorontsov <avorontsov@ru.mvista.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* Cross-compile with cross-gcc -I/path/to/cross-kernel/include
*/
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static void pabort(const char *s)
{
perror(s);
abort();
}
static const char *device = "/dev/spidev1.1";
static uint32_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;
static int verbose;
uint8_t default_tx[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x40, 0x00, 0x00, 0x00, 0x00, 0x95,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x0D,
};
uint8_t default_rx[ARRAY_SIZE(default_tx)] = {0, };
char *input_tx;
static void hex_dump(const void *src, size_t length, size_t line_size, char *prefix)
{
int i = 0;
const unsigned char *address = src;
const unsigned char *line = address;
unsigned char c;
printf("%s | ", prefix);
while (length-- > 0) {
printf("%02X ", *address++);
if (!(++i % line_size) || (length == 0 && i % line_size)) {
if (length == 0) {
while (i++ % line_size)
printf("__ ");
}
printf(" | "); /* right close */
while (line < address) {
c = *line++;
printf("%c", (c < 33 || c == 255) ? 0x2E : c);
}
printf("\n");
if (length > 0)
printf("%s | ", prefix);
}
}
}
/*
* Unescape - process hexadecimal escape character
* converts shell input "\x23" -> 0x23
*/
static int unescape(char *_dst, char *_src, size_t len)
{
int ret = 0;
char *src = _src;
char *dst = _dst;
unsigned int ch;
while (*src) {
if (*src == '\\' && *(src+1) == 'x') {
sscanf(src + 2, "%2x", &ch);
src += 4;
*dst++ = (unsigned char)ch;
} else {
*dst++ = *src++;
}
ret++;
}
return ret;
}
static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
int ret;
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = len,
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
};
if (mode & SPI_TX_QUAD)
tr.tx_nbits = 4;
else if (mode & SPI_TX_DUAL)
tr.tx_nbits = 2;
if (mode & SPI_RX_QUAD)
tr.rx_nbits = 4;
else if (mode & SPI_RX_DUAL)
tr.rx_nbits = 2;
if (!(mode & SPI_LOOP)) {
if (mode & (SPI_TX_QUAD | SPI_TX_DUAL))
tr.rx_buf = 0;
else if (mode & (SPI_RX_QUAD | SPI_RX_DUAL))
tr.tx_buf = 0;
}
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1)
pabort("can't send spi message");
if (verbose)
hex_dump(tx, len, 32, "TX");
hex_dump(rx, len, 32, "RX");
}
static void print_usage(const char *prog)
{
printf("Usage: %s [-DsbdlHOLC3]\n", prog);
puts(" -D --device device to use (default /dev/spidev1.1)\n"
" -s --speed max speed (Hz)\n"
" -d --delay delay (usec)\n"
" -b --bpw bits per word \n"
" -l --loop loopback\n"
" -H --cpha clock phase\n"
" -O --cpol clock polarity\n"
" -L --lsb least significant bit first\n"
" -C --cs-high chip select active high\n"
" -3 --3wire SI/SO signals shared\n"
" -v --verbose Verbose (show tx buffer)\n"
" -p Send data (e.g. \"1234\\xde\\xad\")\n"
" -N --no-cs no chip select\n"
" -R --ready slave pulls low to pause\n"
" -2 --dual dual transfer\n"
" -4 --quad quad transfer\n");
exit(1);
}
static void parse_opts(int argc, char *argv[])
{
while (1) {
static const struct option lopts[] = {
{ "device", 1, 0, 'D' },
{ "speed", 1, 0, 's' },
{ "delay", 1, 0, 'd' },
{ "bpw", 1, 0, 'b' },
{ "loop", 0, 0, 'l' },
{ "cpha", 0, 0, 'H' },
{ "cpol", 0, 0, 'O' },
{ "lsb", 0, 0, 'L' },
{ "cs-high", 0, 0, 'C' },
{ "3wire", 0, 0, '3' },
{ "no-cs", 0, 0, 'N' },
{ "ready", 0, 0, 'R' },
{ "dual", 0, 0, '2' },
{ "verbose", 0, 0, 'v' },
{ "quad", 0, 0, '4' },
{ NULL, 0, 0, 0 },
};
int c;
c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR24p:v", lopts, NULL);
if (c == -1)
break;
switch (c) {
case 'D':
device = optarg;
break;
case 's':
speed = atoi(optarg);
break;
case 'd':
delay = atoi(optarg);
break;
case 'b':
bits = atoi(optarg);
break;
case 'l':
mode |= SPI_LOOP;
break;
case 'H':
mode |= SPI_CPHA;
break;
case 'O':
mode |= SPI_CPOL;
break;
case 'L':
mode |= SPI_LSB_FIRST;
break;
case 'C':
mode |= SPI_CS_HIGH;
break;
case '3':
mode |= SPI_3WIRE;
break;
case 'N':
mode |= SPI_NO_CS;
break;
case 'v':
verbose = 1;
break;
case 'R':
mode |= SPI_READY;
break;
case 'p':
input_tx = optarg;
break;
case '2':
mode |= SPI_TX_DUAL;
break;
case '4':
mode |= SPI_TX_QUAD;
break;
default:
print_usage(argv[0]);
break;
}
}
if (mode & SPI_LOOP) {
if (mode & SPI_TX_DUAL)
mode |= SPI_RX_DUAL;
if (mode & SPI_TX_QUAD)
mode |= SPI_RX_QUAD;
}
}
int main(int argc, char *argv[])
{
int ret = 0;
int fd;
uint8_t *tx;
uint8_t *rx;
int size;
parse_opts(argc, argv);
fd = open(device, O_RDWR);
if (fd < 0)
pabort("can't open device");
/*
* spi mode
*/
ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);
if (ret == -1)
pabort("can't set spi mode");
ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);
if (ret == -1)
pabort("can't get spi mode");
/*
* bits per word
*/
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't set bits per word");
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't get bits per word");
/*
* max speed hz
*/
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't set max speed hz");
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't get max speed hz");
printf("spi mode: 0x%x\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
if (input_tx) {
size = strlen(input_tx+1);
tx = malloc(size);
rx = malloc(size);
size = unescape((char *)tx, input_tx, size);
transfer(fd, tx, rx, size);
free(rx);
free(tx);
} else {
transfer(fd, default_tx, default_rx, sizeof(default_tx));
}
close(fd);
return ret;
}
```
## 内核态
### DTS配置
```c
&spi0 {
status = "okay";
max-freq = <48000000>; //spi internal clk, don't modify
//dma-names = "tx", "rx"; //enable dma
pinctrl-names = "default"; //pinctrl according to you board
pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0 &spi0_cs1>;
spi_test@00 {
compatible = "rockchip,spi_test_bus0_cs0";
reg = <0>; //chip select 0:cs0 1:cs1
id = <0>;
spi-max-frequency = <24000000>; //spi output clock
//spi-cpha; not support
//spi-cpol; //if the property is here it is 1:clk is high, else 0:clk is low when idle
};
spi_test@01 {
compatible = "rockchip,spi_test_bus0_cs1";
reg = <1>;
id = <1>;
spi-max-frequency = <24000000>;
spi-cpha;
spi-cpol;
};
};
```
### 代码分析
```C
static int __init spi_rockchip_test_init(void)
{
int ret = 0;
misc_register(&spi_test_misc);
ret = spi_register_driver(&spi_rockchip_test_driver);
return ret;
}
module_init(spi_rockchip_test_init);
```
`spi_rockchip_test_init`函数,作为内核模块的初始化函数。在这个函数内部,执行以下操作:调用`misc_register`函数,将`spi_test_misc`结构体注册为一个misc设备。调用`spi_register_driver`函数,将`spi_rockchip_test_driver`结构体注册为一个SPI总线驱动程序。
```c
static struct spi_driver spi_rockchip_test_driver = {
.driver = {
.name = "spi_test",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(rockchip_spi_test_dt_match),
},
.probe = rockchip_spi_test_probe,
.remove = rockchip_spi_test_remove,
};
```
`spi_rockchip_test_driver`的SPI总线驱动程序结构体(`struct spi_driver`)。在这个结构体中,设置了以下成员变量:
- `.driver.name`:驱动程序的名称,设置为`"spi_test"`。
- `.driver.owner`:指向当前内核模块的指针,用于标识驱动程序的所有者。
- `.driver.of_match_table`:指向一个设备树匹配表的指针,用于与设备树中的设备进行匹配。
- `.probe`:指向`rockchip_spi_test_probe`函数的指针,表示当设备被探测到时,将调用该函数进行初始化。
- `.remove`:指向`rockchip_spi_test_remove`函数的指针,表示当设备被移除时,将调用该函数进行清理。
```c
static struct miscdevice spi_test_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "spi_misc_test",
.fops = &spi_test_fops,
};
```
定义了一个名为`spi_test_misc`的Misc设备结构体(`struct miscdevice`)。在这个结构体中,设置了以下成员变量:
- `.minor`:使用`MISC_DYNAMIC_MINOR`宏来动态分配一个未使用的次设备号。
- `.name`:设备的名称,设置为`"spi_misc_test"`。
- `.fops`:指向`spi_test_fops`的指针,将文件操作结构体与Misc设备关联起来。
```c
static const struct file_operations spi_test_fops = {
.write = spi_test_write,
};
```
首先,定义了一个名为`spi_test_fops`的文件操作结构体(`struct file_operations`)。在这个结构体中,只设置了其中的一个成员变量`.write`,将其指向了`spi_test_write`函数。这表明当文件被写入时,会调用`spi_test_write`函数来处理写操作。
```c
static int rockchip_spi_test_probe(struct spi_device *spi)
{
int ret;
int id = 0;
struct spi_test_data *spi_test_data = NULL;
if (!spi)
return -ENOMEM;
if (!spi->dev.of_node)
return -ENOMEM;
spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL);
if (!spi_test_data) {
dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");
return -ENOMEM;
}
spi->bits_per_word = 8;
spi_test_data->spi = spi;
spi_test_data->dev = &spi->dev;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(spi_test_data->dev, "ERR: fail to setup spi\n");
return -1;
}
if (of_property_read_u32(spi->dev.of_node, "id", &id)) {
dev_warn(&spi->dev, "fail to get id, default set 0\n");
id = 0;
}
g_spi_test_data[id] = spi_test_data;
printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, spi->modalias, spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz);
return ret;
}
```
1. 首先,会做一个判空,传入的`spi`指针为空指针,表示没有有效的SPI设备,函数将返回错误码`ENOMEM`,表示内存不足。如果`spi`结构的`dev`成员中的`of_node`为空,表示设备没有有效的设备树节点,函数同样返回错误码`ENOMEM`。
2. 使用`kzalloc`分配了一块内存,大小为`struct spi_test_data`结构的大小。`kzalloc`是一个内核函数,它会将分配的内存区域清零。如果分配失败,将返回错误码`ENOMEM`。如果分配成功,将把指针赋给`spi_test_data`。如果分配失败,函数将打印错误信息,并返回错误码`ENOMEM`。
3. 将SPI设备的`bits_per_word`成员设置为8,表示每个字节使用8个位。
4. 将`spi`指针和`spi->dev`的地址分别赋给`spi_test_data`结构的成员变量`spi`和`dev`。
5. 调用`spi_setup`函数对SPI设备进行设置和初始化。如果返回值小于0,表示设置和初始化失败。函数将打印错误信息,并返回-1。
6. 这里使用`of_property_read_u32`函数从设备树节点中读取名为"id"的属性,并将其值存储在`id`变量中。如果读取失败,将打印警告信息,并将`id`设置为0。
7. 将`spi_test_data`指针存储在全局数组`g_spi_test_data`中的索引为`id`的位置。
8. 使用`printk`函数打印一条包含SPI设备的相关信息的调试信息。
```c
static ssize_t spi_test_write(struct file *file,
const char __user *buf, size_t n, loff_t *offset)
{
int argc = 0, i;
char tmp[64];
char *argv[16];
char *cmd, *data;
unsigned int id = 0, times = 0, size = 0;
unsigned long us = 0, bytes = 0;
char *txbuf = NULL, *rxbuf = NULL;
ktime_t start_time;
ktime_t end_time;
ktime_t cost_time;
memset(tmp, 0, sizeof(tmp));
if (copy_from_user(tmp, buf, n))
return -EFAULT;
cmd = tmp;
data = tmp;
while (data < (tmp + n)) {
data = strstr(data, " ");
if (!data)
break;
*data = 0;
argv[argc] = ++data;
argc++;
if (argc >= 16)
break;
}
tmp[n - 1] = 0;
if (!strcmp(cmd, "setspeed")) {
int id = 0, val;
struct spi_device *spi = NULL;
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", &val);
if (id >= MAX_SPI_DEV_NUM)
return n;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return n;
} else {
spi = g_spi_test_data[id]->spi;
}
spi->max_speed_hz = val;
} else if (!strcmp(cmd, "write")) {
char name[64];
int fd;
mm_segment_t old_fs = get_fs();
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
if (argc > 3) {
sscanf(argv[3], "%s", name);
set_fs(KERNEL_DS);
}
txbuf = kzalloc(size, GFP_KERNEL);
if (!txbuf) {
printk("spi write alloc buf size %d fail\n", size);
return n;
}
if (argc > 3) {
fd = sys_open(name, O_RDONLY, 0);
if (fd < 0) {
printk("open %s fail\n", name);
} else {
sys_read(fd, (char __user *)txbuf, size);
sys_close(fd);
}
set_fs(old_fs);
} else {
for (i = 0; i < size; i++)
txbuf[i] = i % 256;
}
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_write_slt(id, txbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
bytes = size * times * 1;
bytes = bytes * 1000 / us;
printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(txbuf);
} else if (!strcmp(cmd, "read")) {
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
rxbuf = kzalloc(size, GFP_KERNEL);
if (!rxbuf) {
printk("spi read alloc buf size %d fail\n", size);
return n;
}
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_read_slt(id, rxbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
bytes = size * times * 1;
bytes = bytes * 1000 / us;
printk("spi read %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(rxbuf);
} else if (!strcmp(cmd, "loop")) {
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
txbuf = kzalloc(size, GFP_KERNEL);
if (!txbuf) {
printk("spi tx alloc buf size %d fail\n", size);
return n;
}
rxbuf = kzalloc(size, GFP_KERNEL);
if (!rxbuf) {
kfree(txbuf);
printk("spi rx alloc buf size %d fail\n", size);
return n;
}
for (i = 0; i < size; i++)
txbuf[i] = i % 256;
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_write_and_read_slt(id, txbuf, rxbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
if (memcmp(txbuf, rxbuf, size))
printk("spi loop test fail\n");
bytes = size * times;
bytes = bytes * 1000 / us;
printk("spi loop %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(txbuf);
kfree(rxbuf);
} else {
printk("echo id number size > /dev/spi_misc_test\n");
printk("echo write 0 10 255 > /dev/spi_misc_test\n");
printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n");
printk("echo read 0 10 255 > /dev/spi_misc_test\n");
printk("echo loop 0 10 255 > /dev/spi_misc_test\n");
printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n");
}
return n;
}
```
1. 使用`memset`将`tmp`数组清零,然后使用`copy_from_user`从用户空间将数据拷贝到`tmp`数组中。如果拷贝失败,将返回错误码`EFAULT`。
2. 函数通过空格字符将命令和参数分隔开,并将它们存储在参数数组`argv`中。通过循环查找空格字符,并将空格替换为字符串结束符号,然后将下一个字符的地址存储在`argv`数组中。最后,将`tmp`数组的最后一个字符设置为字符串结束符号。
3. 据解析得到的命令,函数执行相应的操作。如果命令是"setspeed",则设置SPI设备的速度。如果命令是"write",则向SPI设备写入数据。如果命令是"read",则从SPI设备读取数据。如果命令是"loop",则进行SPI设备的循环测试。如果命令不匹配上述任何一个条件,则打印命令使用说明。
4. 当命令是"setspeed"时,代码会解析参数并设置指定的SPI设备的速度。
- 使用`sscanf`函数从参数数组`argv`中读取`id`和`val`的值,并将其存储在相应的变量中。
- 检查`id`是否超出最大SPI设备数量的限制。如果超出限制,函数将返回处理的字节数`n`。
- 检查对应的`g_spi_test_data[id]`是否为空,如果为空,则打印错误信息并返回处理的字节数`n`。
- 如果`g_spi_test_data[id]`不为空,将其对应的`spi`设备指针赋值给变量`spi`。
- 将`spi->max_speed_hz`设置为`val`,即设置SPI设备的速度。
5. 当命令是"write"时,代码会向指定的SPI设备写入数据。
- 使用`sscanf`函数从参数数组`argv`中读取`id`、`times`和`size`的值,并将其存储在相应的变量中。
- 如果参数个数大于3,说明还有一个文件名参数,使用`sscanf`函数从参数数组`argv`中读取文件名,并将其存储在`name`数组中。
- 如果参数个数大于3,说明有文件名参数,打开该文件并读取数据到`txbuf`中。
- 调用`ktime_get`函数获取当前时间作为测试开始时间。
- 通过循环调用`spi_write_slt`函数向SPI设备写入数据,循环次数为`times`次,每次写入的数据为`txbuf`,数据大小为`size`。
- 调用`ktime_get`函数获取当前时间作为测试结束时间,并计算测试所花费的时间。
- 通过计算总的数据量和测试时间,计算出传输速度,并打印相关信息。
6. 当命令是"read"时,代码会从指定的SPI设备读取数据。具体步骤与"write"命令类似,不同之处在于使用`spi_read_slt`函数从SPI设备读取数据,并计算读取的速度。
7. 当命令是"loop"时,代码将执行SPI设备的循环测试。
- 使用`sscanf`函数从参数数组`argv`中读取`id`、`times`和`size`的值,并将其存储在相应的变量中。
- 将循环测试的数据填充到`txbuf`数组中,每个字节的值为`i % 256`。
- 调用`ktime_get`函数获取当前时间作为测试开始时间。
- 通过循环调用`spi_write_and_read_slt`函数进行循环测试,循环次数为`times`次,每次向SPI设备写入`txbuf`数据,然后从SPI设备读取`size`字节的数据存储到`rxbuf`中。
- 调用`ktime_get`函数获取当前时间作为测试结束时间,并计算测试时间。
- 通过计算总的数据量和测试时间,计算出传输速度,并打印相关信息。
```c
int spi_write_and_read_slt(int id, const void *tx_buf,
void *rx_buf, size_t len)
{
int ret = -1;
struct spi_device *spi = NULL;
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = len,
};
struct spi_message m;
if (id >= MAX_SPI_DEV_NUM)
return ret;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return ret;
} else {
spi = g_spi_test_data[id]->spi;
}
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
```
`spi_write_and_read_slt`通过SPI总线向指定的SPI设备进行同时写入和读取操作。它使用了`spi_transfer`结构体和`spi_message`结构体来描述数据传输的相关参数,并调用`spi_sync`函数执行SPI设备的同步传输操作,将`spi`和`m`作为参数传入。该函数会阻塞直到传输完成。。
```c
int spi_write_then_read_slt(int id, const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx)
{
int ret = -1;
struct spi_device *spi = NULL;
if (id >= MAX_SPI_DEV_NUM)
return ret;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return ret;
} else {
spi = g_spi_test_data[id]->spi;
}
ret = spi_write_then_read(spi, txbuf, n_tx, rxbuf, n_rx);
return ret;
}
```
这段代码通过SPI总线向指定的SPI设备进行先写后读的操作。它使用了`spi_write_then_read`函数来执行先写后读的操作,并将操作结果返回。
```c
int spi_read_slt(int id, void *rxbuf, size_t n)
{
int ret = -1;
struct spi_device *spi = NULL;
if (id >= MAX_SPI_DEV_NUM)
return ret;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return ret;
} else {
spi = g_spi_test_data[id]->spi;
}
ret = spi_read(spi, rxbuf, n);
return ret;
}
```
`spi_read_slt`通过SPI总线从指定的SPI设备进行读取操作。它使用了`spi_read`函数来执行读取操作,并将操作结果返回。
```c
int spi_write_slt(int id, const void *txbuf, size_t n)
{
int ret = -1;
struct spi_device *spi = NULL;
if (id >= MAX_SPI_DEV_NUM)
return -1;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return -1;
} else {
spi = g_spi_test_data[id]->spi;
}
ret = spi_write(spi, txbuf, n);
return ret;
}
```
spi_write_slt通过SPI总线向指定的SPI设备进行写入操作。它使用了`spi_write`函数来执行写入操作,并将操作结果返回。如果参数不合法或指定的SPI设备不存在,函数会直接返回-1。
### 测试命令
```c
echo write 0 10 255 > /dev/spi_misc_test
echo write 0 10 255 init.rc > /dev/spi_misc_test
echo read 0 10 255 > /dev/spi_misc_test
echo loop 0 10 255 > /dev/spi_misc_test
echo setspeed 0 1000000 > /dev/spi_misc_test
```
```bash
echo 类型 id 循环次数 传输长度 > /dev/spi_misc_test
echo setspeed id 频率(单位 Hz) > /dev/spi_misc_test
```
如果需要,可以自己修改测试 case。
### 常见问题
1. 调试前确认驱动有跑起来
2. 确保 SPI 4 个引脚的 IOMUX 配置无误
3. 确认 TX 送时,TX 引脚有正常的波形,CLK 有正常的 CLOCK 信号,CS 信号有拉低
4. 如果 clk 频率较高,可以考虑提高驱动强度来改善信号
### 完整代码
```C
/*drivers/spi/spi-rockchip-test.c -spi test driver
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/* dts config
&spi0 {
status = "okay";
max-freq = <48000000>; //spi internal clk, don't modify
//dma-names = "tx", "rx"; //enable dma
pinctrl-names = "default"; //pinctrl according to you board
pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0 &spi0_cs1>;
spi_test@00 {
compatible = "rockchip,spi_test_bus0_cs0";
reg = <0>; //chip select 0:cs0 1:cs1
id = <0>;
spi-max-frequency = <24000000>; //spi output clock
//spi-cpha; not support
//spi-cpol; //if the property is here it is 1:clk is high, else 0:clk is low when idle
};
spi_test@01 {
compatible = "rockchip,spi_test_bus0_cs1";
reg = <1>;
id = <1>;
spi-max-frequency = <24000000>;
spi-cpha;
spi-cpol;
};
};
*/
/* how to test spi
* echo write 0 10 255 > /dev/spi_misc_test
* echo write 0 10 255 init.rc > /dev/spi_misc_test
* echo read 0 10 255 > /dev/spi_misc_test
* echo loop 0 10 255 > /dev/spi_misc_test
* echo setspeed 0 1000000 > /dev/spi_misc_test
*/
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/fs.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/spi/spi.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/miscdevice.h>
#include <linux/hrtimer.h>
#include <linux/platform_data/spi-rockchip.h>
#include <asm/uaccess.h>
#include <linux/syscalls.h>
#define MAX_SPI_DEV_NUM 6
#define SPI_MAX_SPEED_HZ 12000000
struct spi_test_data {
struct device *dev;
struct spi_device *spi;
char *rx_buf;
int rx_len;
char *tx_buf;
int tx_len;
};
static struct spi_test_data *g_spi_test_data[MAX_SPI_DEV_NUM];
int spi_write_slt(int id, const void *txbuf, size_t n)
{
int ret = -1;
struct spi_device *spi = NULL;
if (id >= MAX_SPI_DEV_NUM)
return -1;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return -1;
} else {
spi = g_spi_test_data[id]->spi;
}
ret = spi_write(spi, txbuf, n);
return ret;
}
int spi_read_slt(int id, void *rxbuf, size_t n)
{
int ret = -1;
struct spi_device *spi = NULL;
if (id >= MAX_SPI_DEV_NUM)
return ret;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return ret;
} else {
spi = g_spi_test_data[id]->spi;
}
ret = spi_read(spi, rxbuf, n);
return ret;
}
int spi_write_then_read_slt(int id, const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx)
{
int ret = -1;
struct spi_device *spi = NULL;
if (id >= MAX_SPI_DEV_NUM)
return ret;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return ret;
} else {
spi = g_spi_test_data[id]->spi;
}
ret = spi_write_then_read(spi, txbuf, n_tx, rxbuf, n_rx);
return ret;
}
int spi_write_and_read_slt(int id, const void *tx_buf,
void *rx_buf, size_t len)
{
int ret = -1;
struct spi_device *spi = NULL;
struct spi_transfer t = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = len,
};
struct spi_message m;
if (id >= MAX_SPI_DEV_NUM)
return ret;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return ret;
} else {
spi = g_spi_test_data[id]->spi;
}
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
static ssize_t spi_test_write(struct file *file,
const char __user *buf, size_t n, loff_t *offset)
{
int argc = 0, i;
char tmp[64];
char *argv[16];
char *cmd, *data;
unsigned int id = 0, times = 0, size = 0;
unsigned long us = 0, bytes = 0;
char *txbuf = NULL, *rxbuf = NULL;
ktime_t start_time;
ktime_t end_time;
ktime_t cost_time;
memset(tmp, 0, sizeof(tmp));
if (copy_from_user(tmp, buf, n))
return -EFAULT;
cmd = tmp;
data = tmp;
while (data < (tmp + n)) {
data = strstr(data, " ");
if (!data)
break;
*data = 0;
argv[argc] = ++data;
argc++;
if (argc >= 16)
break;
}
tmp[n - 1] = 0;
if (!strcmp(cmd, "setspeed")) {
int id = 0, val;
struct spi_device *spi = NULL;
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", &val);
if (id >= MAX_SPI_DEV_NUM)
return n;
if (!g_spi_test_data[id]) {
pr_err("g_spi.%d is NULL\n", id);
return n;
} else {
spi = g_spi_test_data[id]->spi;
}
spi->max_speed_hz = val;
} else if (!strcmp(cmd, "write")) {
char name[64];
int fd;
mm_segment_t old_fs = get_fs();
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
if (argc > 3) {
sscanf(argv[3], "%s", name);
set_fs(KERNEL_DS);
}
txbuf = kzalloc(size, GFP_KERNEL);
if (!txbuf) {
printk("spi write alloc buf size %d fail\n", size);
return n;
}
if (argc > 3) {
fd = sys_open(name, O_RDONLY, 0);
if (fd < 0) {
printk("open %s fail\n", name);
} else {
sys_read(fd, (char __user *)txbuf, size);
sys_close(fd);
}
set_fs(old_fs);
} else {
for (i = 0; i < size; i++)
txbuf[i] = i % 256;
}
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_write_slt(id, txbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
bytes = size * times * 1;
bytes = bytes * 1000 / us;
printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(txbuf);
} else if (!strcmp(cmd, "read")) {
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
rxbuf = kzalloc(size, GFP_KERNEL);
if (!rxbuf) {
printk("spi read alloc buf size %d fail\n", size);
return n;
}
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_read_slt(id, rxbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
bytes = size * times * 1;
bytes = bytes * 1000 / us;
printk("spi read %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(rxbuf);
} else if (!strcmp(cmd, "loop")) {
sscanf(argv[0], "%d", &id);
sscanf(argv[1], "%d", ×);
sscanf(argv[2], "%d", &size);
txbuf = kzalloc(size, GFP_KERNEL);
if (!txbuf) {
printk("spi tx alloc buf size %d fail\n", size);
return n;
}
rxbuf = kzalloc(size, GFP_KERNEL);
if (!rxbuf) {
kfree(txbuf);
printk("spi rx alloc buf size %d fail\n", size);
return n;
}
for (i = 0; i < size; i++)
txbuf[i] = i % 256;
start_time = ktime_get();
for (i = 0; i < times; i++)
spi_write_and_read_slt(id, txbuf, rxbuf, size);
end_time = ktime_get();
cost_time = ktime_sub(end_time, start_time);
us = ktime_to_us(cost_time);
if (memcmp(txbuf, rxbuf, size))
printk("spi loop test fail\n");
bytes = size * times;
bytes = bytes * 1000 / us;
printk("spi loop %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);
kfree(txbuf);
kfree(rxbuf);
} else {
printk("echo id number size > /dev/spi_misc_test\n");
printk("echo write 0 10 255 > /dev/spi_misc_test\n");
printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n");
printk("echo read 0 10 255 > /dev/spi_misc_test\n");
printk("echo loop 0 10 255 > /dev/spi_misc_test\n");
printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n");
}
return n;
}
static const struct file_operations spi_test_fops = {
.write = spi_test_write,
};
static struct miscdevice spi_test_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "spi_misc_test",
.fops = &spi_test_fops,
};
static int rockchip_spi_test_probe(struct spi_device *spi)
{
int ret;
int id = 0;
struct spi_test_data *spi_test_data = NULL;
if (!spi)
return -ENOMEM;
if (!spi->dev.of_node)
return -ENOMEM;
spi_test_data = (struct spi_test_data *)kzalloc(sizeof(struct spi_test_data), GFP_KERNEL);
if (!spi_test_data) {
dev_err(&spi->dev, "ERR: no memory for spi_test_data\n");
return -ENOMEM;
}
spi->bits_per_word = 8;
spi_test_data->spi = spi;
spi_test_data->dev = &spi->dev;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(spi_test_data->dev, "ERR: fail to setup spi\n");
return -1;
}
if (of_property_read_u32(spi->dev.of_node, "id", &id)) {
dev_warn(&spi->dev, "fail to get id, default set 0\n");
id = 0;
}
g_spi_test_data[id] = spi_test_data;
printk("%s:name=%s,bus_num=%d,cs=%d,mode=%d,speed=%d\n", __func__, spi->modalias, spi->master->bus_num, spi->chip_select, spi->mode, spi->max_speed_hz);
return ret;
}
static int rockchip_spi_test_remove(struct spi_device *spi)
{
printk("%s\n", __func__);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id rockchip_spi_test_dt_match[] = {
{ .compatible = "rockchip,spi_test_bus0_cs0", },
{ .compatible = "rockchip,spi_test_bus0_cs1", },
{ .compatible = "rockchip,spi_test_bus1_cs0", },
{ .compatible = "rockchip,spi_test_bus1_cs1", },
{ .compatible = "rockchip,spi_test_bus2_cs0", },
{ .compatible = "rockchip,spi_test_bus2_cs1", },
{ .compatible = "rockchip,spi_test_bus3_cs0", },
{ .compatible = "rockchip,spi_test_bus3_cs1", },
{ .compatible = "rockchip,spi_test_bus4_cs0", },
{ .compatible = "rockchip,spi_test_bus4_cs1", },
{},
};
MODULE_DEVICE_TABLE(of, rockchip_spi_test_dt_match);
#endif /* CONFIG_OF */
static struct spi_driver spi_rockchip_test_driver = {
.driver = {
.name = "spi_test",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(rockchip_spi_test_dt_match),
},
.probe = rockchip_spi_test_probe,
.remove = rockchip_spi_test_remove,
};
static int __init spi_rockchip_test_init(void)
{
int ret = 0;
misc_register(&spi_test_misc);
ret = spi_register_driver(&spi_rockchip_test_driver);
return ret;
}
module_init(spi_rockchip_test_init);
static void __exit spi_rockchip_test_exit(void)
{
misc_deregister(&spi_test_misc);
return spi_unregister_driver(&spi_rockchip_test_driver);
}
module_exit(spi_rockchip_test_exit);
MODULE_AUTHOR("Luo Wei <lw@rock-chips.com>");
MODULE_AUTHOR("Huibin Hong <hhb@rock-chips.com>");
MODULE_DESCRIPTION("ROCKCHIP SPI TEST Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:spi_test");
```
相关问答
nand spi 与 raw区别?RAW是动态内存ROW是只读内存。NAND闪存用于几乎所有可擦除的存储卡。RAW是动态内存ROW是只读内存。NAND闪存用于几乎所有可擦除的存储卡。
看音标写单词/kli:n//i:t//tri:// spi :k//li:v//i’reisの//...[最佳回答]/li:v/live/spi:k/speak/him/him/‘evri/every/i:t/it我只会这几个,其他的您问高手吧!参考资料:me/li:v/live/spi:k/spe...
SPI 数据发送的程序?voidwritedata(unsignedchar*pt,unsignedintn)//pt为输入字节的头地址,n为输入字节的个数{unsignedinti=8;unsignedchartem...
谁能解释一下 spi 协议啊?SPI总线有4根线,分别是SCK,SDO,SDI,SS,可以挂多个从设备,但是在挂多个从设备时,主设备端还需要做一个n选一的译码器,用于选择将要访问的从设备,因此,主...SPI...
spean是什么意思?spean英[spiːn]美[spi:n]vt.[主苏格兰英语];使断奶spean英[spiːn]美[spi:n]vt.[主苏格兰英语];使断奶
SPI 类型的文件肿么打开?-ZOL问答其实SPI文件就是一个DLL文件,和exe一样,是二进制文件,用可以打开二进制文件的工具都能打开,比如WinHex,UE等有用(0)回复dsbswsacnryp试试看看ODBC数据源管理...
刚买的思科路由器wrt300n怎么设置啊?一般路由器的设置,是要把路由器和电脑用网线连接:输入ip地址192.168.1.1或者是192.168.0.1然后在wan选项卡里面选择pppoe输入正确的宽带帐号和密码,然后可以顺...
能烧录GD5F1GQ4UAY编程器有哪些?这个芯片是SPINANDFlash,特点是接口只要3跟线,同时GD的同系列的芯片容量较大,可以到528M。我这里有SmartPRO6000F可以支持这款芯片的烧录,截图供参考:这...
spi 和i2c有什么区别?1、定义不同SPI:SPI是串行外设接口(SerialPeripheralInterface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯...
单片机用一个IO口采集多个按键信号,如何实现?在设计中如果用到IO口不够用,我们肯定第一时间想到的就是通过电阻分压,根据按下不同按键,来让不同阻值的电阻接到分压电路中,然后ADC模块做电压数据读取分析...