您的位置 首页 维修知识

tcl冰箱eff故障代码(tcl冰箱报ff故障)

本文目录一览 1.微软尴尬了,大部分Windows7用户拒绝升到Windows 10|12月2日坏消息榜 2.…

本文目录一览

1.微软尴尬了,大部分Windows7用户拒绝升到Windows 10|12月2日坏消息

关注钛媒体每日、每月整理发布的行业坏消息榜,一榜略尽当日当月最具影响的坏消息。

美国互联网调查公司Net Applications公布报告称,微软Windows 7操作系统的大部分PC用户拒绝升级到Windows 10系统,尽管后者只需免费升级。

互联网流量分析公司StatCounter的数据则显示, Windows 10在上月是全球使用量第三多的桌面操作系统。然而Net Applications却称,Windows 10只能排到第四,仍然落后于具有14年历史的Windows XP。不过,可以确认的是,两家市场分析公司均认为Windows 7是全球使用最多的桌面操作系统。

据公开的数据显示,11月56.11%的联网PC用户仍在使用Windows 7,这也意味全球一半以上的电脑还在使用这款已有6年历史的操作系统,可以看出Windows 7广受用户欢迎。

微软是今年7月29日全球发布Windows 10,供Windows 7和Windows 8的PC用户在一年内免费升级。虽然用户对Windows 10的反应基本上都是正面的,但大部分Windows 7用户都仍在使用这个已有6年历史的操作系统。自Windows 10发布以来,使用Windows 7的PC用户比例几乎没什么改变:8月为57.7%,9月为56.5%,10月为55.7%。

与此同时,Windows 8系统的PC用户人数则有所增加,11月所占比例为14%,高于10月的13.2%。在Windows 10发布以前的6月,Windows 8所占比例为16%。

在发布4个月后,Windows 10的市场份额已经上升至11月的9%,相比之下10月为7.9%。Windows 10采用速度缓慢的另一原因则在于,预装该系统的新款PC的销售表现不佳。

由此可以看出,虽然微软在发布Windows 10时表示Windows 7和Windows 8的PC用户在一年内免费升级,希望借此快速提升该操作系统的市场使用率,但从现在的情况来看,消费者升级的欲望并不强烈。

当然,微软希望Windows 10的市场份额在未来几个月继续保持增长,尤其当企业用户纷纷开始升级到Windows 10。不过,即使顺风顺水,Windows 10要想实现接近Windows 7的水平,花费12个月以上的时间在所难免。微软此前希望在2017年实现Windows 10在10亿台设备上运行的目标,但基于目前增长缓慢的情况,这一目标能否实现还有待观察。

今日坏消息榜单 NO.2-NO.7

谷歌教育服务被指侵犯隐私 EFF请求FTC调查

国际非营利性数字版权和法律组织电子前线基金会(EFF)周二向美国联邦贸易委员会提交申请,恳请委员会对谷歌的热门教育服务展开调查。基金会声称谷歌的这项服务违反了其限制性使用学生数据的承诺,侵犯了学生们的隐私权。电子前线基金会表示,这项名为Google Apps for Education的服务违反了谷歌在1月份签署的承诺书。谷歌在1月份与包括苹果和微软在内的75家公司签署了具有法律约束力的Student Privacy Pledge承诺书,规定它只能出于教育目的去搜集、存储或使用学生的数据。谷歌已经对此作出回应,声称它已经同意修改出售给学校的Chromebook的同步设置。在新设置下,学生在使用Google Apps for Education时输入的数据或由学生生成的数据将不会被用于他处。

IDC:今年全球平板电脑出货量将下滑8%

美国市场研究公司IDC的最新数据显示,由于分体式平板电脑的增长尚不足以抵消整个市场的下滑,2015年全球平板电脑出货量有可能较去年下滑8.1%。IDC表示,2015年全球平板电脑出货量预计为2.113亿台,但今年已经连续3个季度下滑。平板电脑销量之所以萎缩,一定程度上源自智能手机市场的规模和复杂程度增加。尽管出现下滑,但IDC表示,整个平板电脑市场仍在向分体式平板电脑转移,这类设备的主要特征是配有可选键盘。IDC预计,分体式平板电脑销量2015年将增长75%,2016年可能翻番。“随着苹果、谷歌和微软等厂商纷纷推出相应的产品,我们开始看到竞争对该领域的影响。”IDC平板电脑部门研究总监珍·菲利普·布沙尔说。

东芝收缩中国电视销售业务 或将退出海外市场

据日本共同社12月2日透露,东芝已减持与中国家电巨头TCL集团合资的液晶电视销售公司的股份,持股比例从51%降至30%,不再列入合并财务报表。东芝计划对持续苦战的中国电视销售减少干预,缩减业务,将来转变成以品牌授权费作为主要收入来源。这是东芝彻底调整中国业务的举措之一。东芝已在欧美市场退出自主研发、生产和销售,将品牌授权给向欧美市场销售的台湾厂商。如果对中国的业务调整计划得以实现,就意味着东芝将从主要海外市场退出电视业务。东芝在2010年成立液晶电视销售公司时的持股比例为51%,但2014年已降至30%。预计余下的股份如何处理将与TCL进行协商。

申通被曝存13处安全漏洞 黑客盗取信息逾3万条

黑客利用申通快递公司的管理系统漏洞,侵入该公司服务器,非法获取了3万余条个人信息,之后非法出售。审查此案的上海市青浦区检察院检察官称,买这些信息的人,多数是为了行骗。据检察官透露,黑客是看到著名安全网“乌云网”公布的申通公司系统漏洞后作案的。记者随后搜索“乌云网”发现,2013年以来,该网站至少公布了申通公司与信息泄露隐患有关的漏洞报告13篇,涉及系统弱口令、服务器目录、管理后台、快递短信等各个方面,其中9份报告被标注的危害等级为“高”。根据上海市青浦区检察院指控,鞠某为非法牟利,利用申通K8速运管理系统漏洞,非法侵入申通快递有限公司服务器,下载包含公民个人信息的快递面单信息3万余条。后通过网络出售给他人,非法获利3万余元。

朋友圈低价苹果手机骗局:支付完就拉黑

不久前公安局反电信诈骗专线接到一名男子报警称,他在微信朋友圈看到一则“低价销售苹果手机”的信息,联系卖家谈妥价格,以“2000元一台”的价格向卖家购买苹果6手机。他通过“微信支付”多次向对方支付“订金”合计7000元。转账后,对方迅速转变态度,并要求他继续转账,否则不发货。他发现被骗后要求对方退款,对方将其拉黑。据了解,无论是苹果(中国)官网、苹果(香港)官网、京东商城或是天猫(淘宝)商城,从网上正规渠道购买苹果6手机,价格普遍在4000元左右。由于价格偏贵,多数消费者希望能从其他途径买到便宜点的苹果手机。于是,一大批“低价销售苹果手机”的“网店”如雨后春笋般冒了出来,当然骗子们也看到了“商机”,于是就出现了利用微信朋友圈“低价卖苹果手机”的骗局。

阿黛尔在线购票粉丝信用卡信息外泄

著名女歌星阿黛尔的众多英国粉丝称,当他们试图在网上购买她的欧洲巡演门票时,却意外拿到了其他人的信用卡信息。几名粉丝向BBC表示,当他们试图完成购买门票的交易时,结果却发现自己被带到了其他演唱会的门票付款页面,而不是他们所选择的演唱会。同时,这个页面显示的是其他人的个人信息,而不是他们自己的信息。负责此次门票销售工作的是演唱会追踪和票务服务Songkick,发生上述情况可能是因为该服务的系统存在故障。【张娜/钛媒实习编辑】

2.「芯观点」东芝半导体之困:孱弱的“主人”与凶恶的“客人”

一年多以来,有着近150年历史的百年老店日本东芝高层权斗大戏似乎从未有休止符,也没有按下暂停键的意思。10个月之内公司CEO位置走马灯一般有了三个不同的面孔,从去年11月初拟定的“一分为三”拆分法到今年2月份的“一分为二”,却始终未能摆脱被海外(主要是美国)私募股权基金收购之后退市的阴影。

这场旷日持久的连续剧背后的角力者笼统概括起来主要有两方:一个是以日本本土股东财团,以及背后为他们撑腰的日本政府(经产省、安倍晋三家族、菅义伟家族)势力;一个是庞大分散的,虽无清晰一致利益诉求,但在抵制本土技术保护主义浪潮中暂时结成利益共同体的积极股东们(activist investors),双方持股占比基本为50%:50%。如果从地域以及历史的角度看,前者可以被视为“主”,后者为“客”,主人力图通过拆家砌墙下逐客令,不甘屈服的客人们也想着反客为主,至少达到短期利益不受损的目的,双方争斗日趋白热化。

庞大的东芝工业集团将走向何方,以及东芝最优质的核心资产——存储与功率半导体的发展前景会如何,仍处在极度混沌之中。

谁是凶恶的“家奴”?东芝为何引狼入室?

彻底引爆主宾之争的导火索始于2020年的7月。

在当月公司召开的年度股东大会(AGM)上,公司最大股东的“将外部董事加入董事会”的提议被彻底否决。东芝最大股东就是总部位于新加坡的Effissimo Capital Management(以下简称Effissimo),他们持有东芝集团约9.9%的股份。对于此事,里昂证券(CLSA)分析师尼古拉斯·史密斯(Nicholas Smith)当时便撰文分析,通过大数据发现,当年很多大型公司的AGM由于疫情管控改为线上方式之后,很多股东无法亲自到场成了很多“黑手”操纵董事会提议的手段,这些大型公司的AGM有高达85%的股东大会都因不可名状的疫情管控出现了重大人员缺席,东芝董事会内部的本土势力巧妙利用了这一点,削弱了Effissimo在股东大会上发起议案的力量。

不肯善罢甘休的Effissimo联合其他海外的积极股东,选出了三名独立调查员调查此事,并且起草了一份接近140页的报告,将东芝集团主客双方难以调和的矛盾大白于天下:就在AGM召开前的两个月,公司CEO车谷畅昭在一次早餐会上与日本首相菅义伟密会,随后日本经产省(METI)站出来放风,认为日本目前的外资管理法和外汇法已经过时了,亟需修订以保护日本优质的本土资产,食髓知味的东芝显然是被保护的重点对象,这就给2个月后的东芝AGM排外的动作埋下了伏笔。

这个洋洋洒洒的报告以“东芝CEO如何勾结政府高层欺压海外投资者”为主题,瞬间给本来还不怎么抱团的积极股东们唤醒了2015年东芝财务造假的集体记忆,沸腾的舆论之下,在报告正式被披露的两个月前,即2021年4月份,车谷畅昭黯然辞职,前首席运营官纲川智(Satoshi Tsunakawa)担任新CEO。

前东芝董事长永山治于去年6月被罢免(图源:日经亚洲)

6月份正式披露的文件显示,通过搜索778000 封电子邮件和附件,发现此事有九个地方与安倍晋三和菅义伟组阁下的日本经产省相关,经产省还授意威胁另一个海外重要投资者3D Investment Partners与大股东Effissimo划清界限:“如果你们和Effissimo站在一起,就别怪我们动用政府力量来查你。”

可见,2021年6月,东芝的主与客彻底撕破了脸,董事会权力架构也随即进行了重大洗牌。董事会主席永山治(Osamu Nagayama)被罢免,审计委员会主席太田順司及另一名委员会成员山内卓被从候选人名单中除名。这些事态发展意味着东芝在5月提名的最初13名候选人中只有8名将进入董事会。

东芝目前的五人提名委员会现在包括三名非日本成员,由George Zage领导,他是股东 Farallon Capital前亚洲区主管。担任新战略审查委员会主席的是前毕马威香港高级合伙人 Paul Brough,审计委员会主席由前杜邦公司日本高管桥本胜则担任。

这种“双头怪”式的公司董事会高层架构,为后来在如何拆分、是否拆分和收购的多元选择的无限扯皮中埋下了伏笔。

行文至此,东芝本土化派的眼中钉的面目已经十分清晰了,他们由三大海外基金为首,聚拢起了更多的欧美小股东。集微网通过各种资料汇总,整理如下:

Effissimo、3D 和 Farallon 共同拥有东芝约四分之一的股份。另外的海外知名机构投资者包括持有 1.22% 股份的挪威主权财富基金、持有 0.43%股份的加州公共雇员退休基金和持有 0.22% 股份的佛罗里达州管理委员会。

以上,就是以东芝CEO未代表的本土派眼中的“积极股东”。据估计,所有外国积极基金合计持有30%,而更广泛的海外投资者拥有东芝50%的股份。如前文所述,George Zage是 Farallon Capital前亚洲区主管,他担任重要的提名委员会的主管,背后的势力足以和本土派相拮抗。

有着近150年历史的东芝和索尼、松下一样代表着日本民族企业之光,那么为何当下企业内部出现了如此重大的主客分裂?如果东芝有一以贯之且清晰的核心优质资产保护路线,有序推进《外汇法》和《外国投资法》改革,就不至于让众多海外投资者抱团形成和本土派对抗的架势。背后的深层次原因则要追溯到2011年大地震和大海啸引发的核电危机。

有关过去十多年来东芝核电危机的分析文章数不胜数,本文不在赘述,以事后之见来看,东芝收购美国西屋电气以失败告终,不惜铤而走险财务造假,被揭穿之后脸面尽失。为了在全球范围内挽回商誉,从2017年开始,东芝管理层便一直面临着海外激进基金和投资者的压力,该公司陆续向数十家外国对冲基金出售了6000亿日元(按当前汇率计算为52.9亿美元)的股票,并且当时的安倍晋三执政团队为了恢复东芝的元气,承诺让董事会多元化并保护海外中小股东的利益。

但形势总归比人强,当东芝在2020年宣布包括股票回购和战略收购在内的1万亿日元投资闪电战时,这些海外投资者已经表达了不满,并呼吁立即召开股东大会。

这些活跃的海外投资者(activist investors)也许和本土派最大的不同点,就在于他们非常关注短期利益回报,对公司长期发展战略缺乏足够的耐心和兴趣,而且他们的欧美血统本能地对大政府行为保持着警惕和反感(尽管这些投资者本身也有不少是主权基金),风吹草动之下,认定当年安倍的承诺是一种缓兵之计,不甘当待宰羔羊,以鼓动东芝答应海外私募基金收购邀约的方式逼迫本土派就范,使其放弃拆分计划。

图穷匕见的“一分为二”拆分计划恐难实现

去年11月,东芝临时看守CEO纲川智突然宣布了企业的拆分计划,将拥有的七个业务部门,公共交通系统、供水和污水系统以及电梯的基础设施部门、处理火力和核电站建设的能源部门以及电源管理、硬盘驱动器为代表的半导体部门一分为三。

这则消息公布的时间恰好选在美国通用电气也出台了把自己一分为三的计划之后,东芝卡着这一时间点,也是为了顺应舆论走向,给外界一种大型工业集团的历史已经是过去式,未来必然要向西门子拆分出英飞凌,飞利浦拆分出恩智浦等等趋势看齐。

在海外activist investors的强烈阻力之下,今年2月份,东芝拆分计划从一分为三改成了一分为二,用一句话概括,基本就是存储半导体(SSD,以及监督当下持有的铠侠40%股份的剥离)为一部分,剩下的为另一部分。东芝官方给出的理由是,一分为三拆分成本太高,估算下来大约需要200亿日元,分成两个,省下来的钱可以回报给中小股东。

其实这次东芝不再遮遮掩掩,而是图穷匕见,由阴谋换成了阳谋:半导体业务一定要剥离出来,由本土派执掌未来航向。美光和西部数据密谋收购铠侠的大背景,客观上加速了东芝向半导体业务重心偏斜的操作。

对此,集微咨询咨询研究总监赵翼认为:“大型企业拆分上市确实也算一种趋势,从二级市场角度来说,不同业务的混在一起不利于资本市场对它的进行估值,国内的厂商比如比亚迪微电子,TCL等都有类似举动,而且剥离之后,也有利于培养下游客户的稳定性。但至于东芝的另一种结果,即被海外私募基金收购,我觉得可能性也不大,毕竟现在日本也已经把半导体业务上升到了国家战略,不会轻易让优质资产脱手。”

赵翼还指出,海外投资者反对拆分的另一大重要原因是,担心自己在东芝半导体业务中的股权被稀释而拿不到足够的短期回报。毕竟,从近期东芝财报上看(如上图),半导体存储业务是最亮眼的七大板块中,2021财年前三季度电子存储占到总净销售额(net sales)的近29%,并且增长率为28%,明显高于其他业务。

结语东芝主动吞下“毒丸”,却无法解毒

毒丸防御曾是针对恶意收购的一种防御措施,由公司董事会事先通过一项股权摊薄条款,一旦敌意方收购公司一定比例的股份(通常是10%至20%的股份),即触发该条款生效,使公司原有股东可以较低的价格获得公司大量股份,从而抬高收购方的成本。

2015年以来,受困于核电危机泥潭和财务造假丑闻,急于重振商誉的东芝选择吞下让渡企业发展主权的毒丸,作为缓兵之计。然而,这批增持股份的海外基金投资者成了东芝本土派眼中难以逾越的障碍,在目前拟定的2023年完成拆分的蓝图需要三分之二的重要股东投票才能通过,新任CEO岛田太郎三月份走马上任之后在拆分计划问题上萧规曹随,但海外三巨头毫无疑问已经按下了否决键,东芝半导体部门以及铠侠的未来,和日本的重振国家半导体法案一样,充满了太多的不确定性。(校对/Aaron)

3.pypy为什么能让python比c还快?读完此文相信你就懂了

最近 “pypy为什么能让python比c还快” 刷屏了,原文讲的内容偏理论,干货比较少。我们可以再深入一点点,了解pypy的真相。

话不多说,正式开始,本文包括下面几个部分:

  • 语言分类
  • python的解释器实现
  • pypy为什么快
  • 性能比较
  • 性能优化方法
  • pypy的特性
  • 小结

语言分类

我们先从最基本的一些语言分类概念聊起,对这部分内容非常了解的朋友可以跳过。

静态语言 vs 动态语言

如果在编译时知道变量的类型,则该语言为静态类型。静态类型语言的常见示例包括Java,C,C ++,FORTRAN,Pascal和Scala。在静态类型语言中,一旦使用类型声明了变量,就无法将其分配给其他不同类型的变量,这样做会在编译时引发类型错误。

# java

int data;
data = 50;
data = “Hello Game_404!”; // causes an compilation error
复制代码

如果在运行时检查变量的类型,则语言是动态类型的。动态类型语言的常见示例包括JavaScript,Objective-C,PHP,Python,Ruby,Lisp和Tcl。 在动态类型语言中,变量在运行时通过赋值语句绑定到对象,并且可以在程序执行期间将相同的变量绑定到不同类型的对象。

# python

data = 10;
data = "Hello Game_404!"; // no error caused
data = data + str(10)
复制代码

一般来说静态语言编译成字节码执行,动态语言使用解释器执行。编译型语言性能更高,但是较难移植到不同的CPU架构体系和操作系统。解释型语言易于移植,性能会比编译语言要差得多。这是频谱的两个极端。

强类型语言 vs 弱类型语言

强类型语言是一种变量被绑定到特定数据类型的语言,如果类型与表达式中的预期不一致,将导致类型错误,比如下面这个:

# python

temp = “Hello Game_404!”
temp = temp + 10; // program terminates with below stated error (TypeError: must be str, not int)
复制代码

python和我们感觉不一致,背叛了弱类型语言,不像世界上最好的语言:(

# php

$temp = “Hello Game_404!”;
$temp = $temp + 10; // no error caused
echo $temp;
复制代码

这一部分内容主要翻译自参考链接1

python的解释器实现

python是一门动态编程语言,由特定的解释器解释执行。下面是一些解释器实现:

  • CPython 使用c语言实现的解释器
  • PyPy 使用python语言的子集RPython实现的解释器,一般情况下PyPy比CPython快4.2倍
  • Stackless Python 带有协程实现的解释器
  • Jython Java实现的解释器
  • IronPython .net实现的解释器
  • Pyston 一个较新的实现,是CPython 3.8.8的一个分支,具有其他针对性能的优化。它针对大型现实应用程序(例如Web服务),无需进行开发工作即可提供高达30%的加速。

还有几个相关概念:

  • IPython && Jupyter ipython是使用python构建的交互式shell, Jupyter是其web化的包装。
  • Anaconda 是一个python虚拟环境,Python数据科学常用。
  • mypyc 一个新的项目,将python编译成c代码库,以期提高python的运行效率。
  • py文件和pyc文件 pyc文件是python编译后的字节码,也可以由python解释器执行。
  • wheel文件和egg文件 都是项目版本发布的打包文件,wheel是最新标准。

这里大家会有一个疑问,python不是解释型语言嘛?怎么又有编译后的pyc。是这样的: py文件编译成pyc后,解释器默认 优先 执行pyc文件,这样可以加快python程序的 启动速度 (注意是启动速度)。继背叛弱类型语言后,python这个鬼又在编译语言和解释语言之间横跳。

还有一个事件是Go语言在1.5版本实现自举。Go语言在1.5版本之前使用c实现的编译器,在1.5版本时候使用Go实现了自己的编译器,这里有一个鸡生蛋和蛋生鸡的过程,也挺有意思。

pypy为什么快

pypy使用python的子集rpython实现了解释器,和前面介绍的Go的自举有点类似。反常识的是rpython的解释器会比c实现的解释器快? 主要是因为pypy使用了JIT技术。

Just-In-Time (JIT) Compiler 试图通过对机器码进行一些实际的编译和一些解释来获得两全其美的方法。简而言之,以下是JIT编译为提高性能而采取的步骤:

  1. 标识代码中最常用的组件,例如循环中的函数。
  2. 在运行时将这些零件转换为机器码。
  3. 优化生成的机器码。
  4. 用优化的机器码版本交换以前的实现。

这也是 “pypy为什么能让python比c还快” 一文中的示例展现出来的能力。pypy除了速度快外,还有下面一些特点:

  • 内存使用情况比cpython少
  • gc策略更优化
  • Stackless 协程模式默认支持,支持高并发
  • 兼容性好,高度兼容cpython实现,基本可以无缝切换

以上都是宣称

pypy这么强,快和省都占了,为什么没有大规模流行起来呢? 我个人认为,主要还是python的原因。

  1. python生态中大量库采用c实现,特别是科学计算/AI相关的库,pypy在这块并没有优势。pypy快的主要在pure-python,也就是纯粹的python实现部分。
  2. pypy适合长驻内存的高并发应用(web服务类)
  3. python是一门胶水语言,并不追求性能极致,即使快4倍也不够快:( 。肯定比不上c,原文中的c应该是 偷换了概念 ,指c实现的cpython解释器。

需要注意的是,pypy一样也有GIL的存在, 所以高并发主要在stackless。

这一部分内容参考自参考链接2

性能比较

我们可以编写性能测试用例,用代码说话,对各个实现进行对比。本文的测试用例并不严谨,不过也足够说明一些问题了。

开车和步行

原文中累加测试用例是100000000次,我们减少成1000次:

import time

start = time.time()
number = 0
for i in range(1000):
    number += i

print(number)
print(f"Elapsed time: {time.time() - start} s")
复制代码

测试结果如下表(测试环境在本文附录部分):

解释器

循环次数

耗时(s)

python3

1000

0.00014281272888183594

pypy3

1000

0.00036716461181640625

结果显示运行1000次循环的情况下cpython要比pypy快,这和循环100000000次 相反 。用下面的例子可以非常形象的解释这一点。

假设您想去一家离您家很近的商店。您可以步行或开车。您的汽车显然比脚快得多。但是,请考虑需要执行以下操作:

  1. 去你的车库。
  2. 启动你的车。
  3. 让汽车暖一点。
  4. 开车去商店。
  5. 查找停车位。
  6. 在返回途中重复该过程。

开车要涉及很多开销,如果您想去的地方在附近,这并不总是值得的!现在想想如果您想去五十英里外的邻近城市会发生什么。开车去那里而不是步行肯定是值得的。

举例来自参考链接2

尽管速度的差异并不像上面类比那么明显,但是PyPy和CPython的情况也是如此。

横向对比

我们横向对比一下c,python3, pypy3, js 和lua的性能。

# js
const start = Date.now();
let number = 0
for (i=0;i<100000000;i++){
	number += i
}
console.log(number)
const millis = Date.now() - start;
console.log(`milliseconds elapsed = `, millis);

# lua
local starttime = os.clock();
local number = 0
local total = 100000000-1
for i=total,1,-1 do
    number = number+i
end
print(number)
local endtime = os.clock();
print(string.format("elapsed time  : %.4f", endtime - starttime));

# c
#include <stdio.h>
#include <time.h>

const long long TOTAL = 100000000;

long long mySum()
{
    long long number=0;
    long long i;
    for( i = 0; i < TOTAL; i++ )
    {
        number += i;
    }
    return number;
}

int main(void)
{
    // Start measuring time
    clock_t start = clock();

    printf("%llu \n", mySum());
    // Stop measuring time and calculate the elapsed time
    clock_t end = clock();
    double elapsed = (end - start)/CLOCKS_PER_SEC;
    printf("Time measured: %.3f seconds.\n", elapsed);
    return 0;
}
复制代码

解释器

循环次数

耗时(s)

c

100000000

0.000

pypy3

100000000

0.15746307373046875

js

100000000

0.198

lua

100000000

0.8023

python3

100000000

10.14592313766479

测试结果可见,c无疑是最快的,秒杀其它语言,这是编译语言的特点。在解释语言中,pypy3表现配得上优秀二字。

内存占用

测试用例中增加内存占用的输出:

p = psutil.Process()
mem = p.memory_info()
print(mem)
复制代码

测试结果如下:

# python3
pmem(rss= 9027584, vms=4747534336, pfaults= 2914, pageins=1)

# pypy3
pmem(rss=39518208, vms=5127745536, pfaults=12188, pageins=58)
复制代码

pypy3的内存占用会比python3要高,这个才科学,用内存空间换了运行时间。当然这个评测并不严谨,实际情况如何,pypy宣称的内存占用较少,我表示怀疑,但是没有证据。

性能优化方法

了解语言的性能比较后,我们再看看一些性能优化的方法,这对在cpython和pypy之间选型有帮助。

使用c函数

python中使用c函数,比如这里的累加可以使用reduce替换,可以提高效率:

def my_add(a, b):
    return a + b

number = reduce(add, range(100000000))
复制代码

解释器

次数

耗时(s)

pypy3

reduce

0.08371400833129883

pypy3

100000000

0.15746307373046875

python3

reduce

5.705173015594482 s

python3

100000000循环

10.14592313766479

结果展示,reduce对cpython和pypy都有效。

优化循环

优化最关键的地方,提高算法效率,减少循环。更改一下累加的需求,假设我们是求100000000以内的偶数的和,下面展示了使用range的步进减少循环次数来提高性能:

try:
    xrange  # python2注意使用xrange是迭代器,而range是返回一个list
except NameError:  # python3
    xrange = range

def test_0():
    number = 0
    for i in range(100000000):
        if i % 2 == 0:
            number += i
    return number

def test_1():
    number = 0

    for i in xrange(0, 100000000, 2):
        number += i
    return number
复制代码

解释器

循环次数

耗时(s)

python3

50000000

2.6723649501800537 s

python3

100000000

6.530670881271362 s

循环次数减半后,有效率显著提升。

静态类型

python3可以使用类型注解,提高代码可读性。类型确定逻辑上对性能有帮助,每次处理数据的时候,不用再进行类型推断。

number: int = 0
for i in range(100000000):
    number += i
复制代码

解释器

循环次数

类型

耗时(s)

python3

100000000

int

9.492593050003052 s

python3

100000000

不定义

10.14592313766479 s

内存相当于一个空间,我们要用不同的盒子去填充它。图中左边部分1,都使用长度为4(想像float类型)的盒子填充,一行一个,速度最快;图中中间部分2,使用长度为3(想像long类型)和长度为1(想像int类型)的箱子,一行2个,也挺快;图中右侧3,虽然箱子长度仍然是3和1,但是由于没有刻度,填充的时候需要试装,所以速度最慢。

算法的魅力

优化到最后,最重量级的内容登场:高斯求和算法。高斯的故事,想必大家都不陌生,下面是算法实现:

def gaussian_sum(total: int) -> int:
    if total & 1 == 0:
        return (1 + total) * int(total / 2)
    else:
        return total * int((total - 1) / 2) + total


# 4999999950000000
number = gaussian_sum(100000000 - 1)
复制代码

解释器

循环次数

耗时(s)

python3

高斯求和

4.100799560546875e-05 s

python3

100000000循环

10.14592313766479

使用高斯求和后,程序秒开。这大概就是业内面试,要考算法的真相,也是算法的魅力所在。

优化的原则

简单介绍一下优化的原则,主要是下面2点:

  1. 使用测试而不是推测。
python3 -m timeit 'x=3' 'x%2'                                  
10000000 loops, best of 5: 25.3 nsec per loop
python3 -m timeit 'x=3' 'x&1'
5000000 loops, best of 5: 41.3 nsec per loop
python2 -m timeit 'x=3' 'x&1'
10000000 loops, best of 3: 0.0262 usec per loop
python2 -m timeit 'x=3' 'x%2'
10000000 loops, best of 3: 0.0371 usec per loop
复制代码

上面示例展示了,求奇偶的情况下,python3中位运算比取模慢,这是个反直觉推测的地方。在我的python冷兵器合集一文中也有介绍。而且需要注意的是,python2和python3表现相反,所以性能优化要实测,注意环境和实效性。

  1. 遵循2/8法则, 不要过度优化,不用赘述。

pypy的特性

pypy还有下面一些特性:

  • cffi pypy推荐使用cffi的方式加载c
  • cProfile pypy下使用cProfile检测性能无效
  • sys.getsizeof pypy的gc方式差异,sys.getsizeof无法使用
  • __slots__ cpython使用的slots,在pypy下失效

使用slots在python对象中,可以减少对象内存占用,提高效率,下面是测试用例:

def test_0():
    class Player(object):

        def __init__(self, name, age):
            self.name = name
            self.age = age

    players = []
    for i in range(10000):
        p = Player(name="p" + str(i), age=i)
        players.append(p)
    return players


def test_1():
    class Player(object):
        __slots__ = "name", "age"

        def __init__(self, name, age):
            self.name = name
            self.age = age

    players = []
    for i in range(10000):
        p = Player(name="p" + str(i), age=i)
        players.append(p)
    return players
复制代码

测试日志如下:

# python3 slots
pmem(rss=10776576, vms=5178499072, pfaults=3351, pageins=58)
Elapsed time:  0.010818958282470703 s

# python3 默认
pmem(rss=11792384, vms=5033795584, pfaults=3587, pageins=0)
Elapsed time:  0.01322031021118164 s

# pypy3 slots
pmem(rss=40042496, vms=5263011840, pfaults=12341, pageins=4071)
Elapsed time:  0.005321025848388672 s

# pypy3 默认
pmem(rss=39862272, vms=4974653440, pfaults=12280, pageins=0)
Elapsed time:  0.004619121551513672 s
复制代码

详细信息可以看参考链接4和5

pypy最重要的特性还是stackless,支持高并发。这里有IO密集型任务(I/O-bound)和CPU密集型任务(compute-bound)的区分,CPU密集型任务的代码,速度很慢,是因为执行大量CPU指令,比如上文的for循环;I / O密集型,速度因磁盘或网络延迟而变慢,这两者之间是有区别的。这部分内容,要介绍清楚也不容易,容我们下章见。

小结

python是一门解释型编程语言,具有多种解释器实现,常见的是cpython的实现。pypy使用了JIT技术,在一些常见的场景下可以显著提高python的执行效率,对cpython的兼容性也很高。如果项目纯python部分较多,推荐尝试使用pypy运行程序。

注:由于个人能力有限,文中示例如有谬误,还望海涵。

附录

测试环境

  • MacBook Pro (16-inch, 2019)(2.6 GHz 六核Intel Core i7)
  • Python 2.7.16
  • Python 3.8.5
  • Python 3.6.9 [PyPy 7.3.1 with GCC 4.2.1 Compatible Apple LLVM 11.0.3 (clang-1103.0.32.59)]
  • Python 2.7.13 [PyPy 7.3.1 with GCC 4.2.1 Compatible Apple LLVM 11.0.3 (clang-1103.0.32.59)]
  • lua#Lua 5.2.3 Copyright (C) 1994-2013 Lua.org, PUC-Rio
  • node#v10.16.3

参考链接

  1. medium.com/android-new…
  2. realpython.com/pypy-faster…
  3. www.pypy.org/index.html
  4. stackoverflow.com/questions/2…
  5. morepypy.blogspot.com/2010/11/eff…

感谢大家的支持和喜欢,小编会每天分享更多Python学习的干货知识给大家,所以大家别忘了关注小编哦。

作者:游戏不存在
链接:https://juejin.cn/post/6961053856104972325
来源:掘金

4.自学STM32 – ADC模数转换

作者:junziyang


(注:如非特別声明,以下笔记内容均针对STM32F103ZET6而言。不同型号,细节可能存在差别)

在头条发技术贴是最没成就感的,为了看到更多有营养的资讯,碰到自己喜欢的技术贴请一定记得,收藏+转发+点赞。

一、 ADC简介

ADC是Analog to digital conversion/converter的简称,意为模数转换/器,是一种将连续的模拟信号转换为离散的数字信号的一种方式或器件。单片机和计算机只能处理数字信号,因此模拟信号必须经过A/D转换才能被计算机处理。A/D转换一般要经过采样、保持、量化和编码四个过程的处理,将模拟量转换成对应的二进制代码并输出。根据工作原理不同,常用的ADC可分为:积分型、逐次逼近型、并行比较型/串并行型、Σ-Δ调制型、电容阵列逐次比较型及压频变换型。STM32F103ZET6内置的是12位的逐次逼近型ADC。有多达18个复用通道,可以测量16路外部信号和2路内部信号。多个信道的A/D转换可以采用单次、连续、扫描或断续模式。ADC的转换结果可以左对齐或右对齐存储于一个16位的数据寄存器中。

ADC带有模拟看门狗功能,在程序中可以基于此检测输入电压是否超出了设定的上下限。ADC的时钟由APB2总线分频得到(见04 时钟系统,图1),最高频率不能超过14MHz。

二、 ADC工作原理

2.1 系统原理图

STM32内部共有3个ADC模块,分别命名为ADC1-3。图1所示为ADC的原理框图。大体可分为4个功能模块:A/D转换模块,输入通道和参考电平,触发源,以及ADC中断单元。

1. A/D转换模块

图1中部为A/D转换模块,是整个ADC的核心。其内部又分为两种通道:常规通道和注入通道。两个通道转换出的数据分别被送入各自的数据寄存器。常规通道最多可有16个,共用1个DR寄存器。而注入通道至多有4个,但每个通道都有自己独立的DR寄存器。所有的DR寄存器都与右侧的地址/数据总线相连,来实现数据的读取。

2. 输入通道和参考电平

模拟信号经由GPIO引脚输入,每个ADC最多可以支持16个外部通道。GPIO复用为ADC输入端口时,每个端口都有一个对应编号(ADCx_IN0-ADCx_IN15),如下表所示。这些通道经由一个复用器后分为两组,常规组和注入组,分别送入A/D转换模块的常规通道和注入通道。常规组最多可以有16个通道,即所有通道可以全为常规通道。而注入组至多只能有4个通道。

ADC通道

GPIO引脚

ADC通道

GPIO引脚

ADC_123_IN0

PA0

ADC12_IN8

PB0

ADC_123_IN1

PA1

ADC12_IN9

PB1

ADC_123_IN2

PA2

ADC12_IN10

PC0

ADC_123_IN3

PA3

ADC12_IN11

PC1

ADC_12_IN4

PA4

ADC12_IN12

PC2

ADC_12_IN5

PA5

ADC12_IN13

PC3

ADC_12_IN6

PA6

ADC12_IN14

PC4

ADC_12_IN7

PA7

ADC12_IN15

PC5

在常规组中的通道被转换过程中,如果有注入组的通道切入, 常规通道的转换会停止,优先执行注入通道的转换。当注入通道的转换执行完毕后,再回到常规通道进行转换。

除了外部通道,ADC还有两个内部通道,ADC1的两个内部通道分别连接芯片内部的温度传感器(ADCx_IN16)和内部参考电压VREFINT(ADCx_IN17)。ADC2和3的内部通道 16 和 17 连接到了内部的 VSS。

逐步逼近式的ADC,是通过与一个参考电压的比较来实现量化的,因此必须提供一个高质量的参考电压作为基准。参考电压的输入引脚共有4个:

  • 2.4V≤ VREFF+ ≤VDDA :模拟输入正极,为ADC提供参考高电平
  • 2.4V≤ VREFF+ ≤3.6V :模拟电源输入
  • VREFF-=VSSA :模式输入负极,为ADC提供参考低电平
  • VSSA :模拟电源地,等于VSS

ADC所能测量的电压范围就是VREF- ≤ VIN ≤ VREF+。按照STM32手册要求,ADC工作时必须将VDDA与VDD相连,VSSA与VSS相连,即低电平接地,高电平通常接3.3V。

3. 触发源

ADC需要一个触发信号来启动A/D转换。触发源可以是控制寄存器、内部定时器或外部IO触发。图1下方所示为ADC的触发源。触发源可以通过相关寄存器进行选择。两组触发源分别与A/D模块内部的常规通道和注入通道相连,触发相应的通道启动转换。

4. 中断单元

图1最上方为ADC的中断单元。ADC1和ADC2的中断被映射到同一个中断向量,ADC3的中断被映射到另一个单独的中断向量。ADC相关的中断比较简单,只有3个,分别是常规通道转换结束(EOC)、注入通道转换结束(JEOC)和模拟看门狗(AWD)事件。

2.2 相关寄存器

与其他外设类似,ADC的功能通过相关寄存器进行配置来实现。ADC的功能说明中会反复提到相关寄存器功能位的设置。为便于后续学习,将所有寄存器整理于此。图2所示为ADC相关的寄存器地址映射与复位值表。从图中可以看出,ADC共占用20个32位寄存器。乍看复杂,一分组就简单明了了。20个寄存器可分为7组,包括:

  • 1个状态寄存器ADC_SR(Status Register),用于管理转换开始、结束、模拟看门狗事件状态。
  • 2个控制寄存器ADC_CRx(Control Register),用于控制寄存器的模式、通道选择、触发源等。
  • 2个取样时间寄存器ADC_SMPRx(Sample time Register),用于设置每个通道的取样时间。
  • 4个注入通道数据偏移寄存器ADC_JOFRx(Offset Register),用来设置4个注入通道的数据偏移量。比如交流信号加了直流偏置,通过设置偏移量,可以将直流分量去掉。直白一点儿说,就是将采集到的数值减去一个预定的数值,这个预定的数值在JOFRx中设置。
  • 2个模拟狗阈值设置寄存器ADC_HTR/LTR(High/Low Threshold Register),HTR由于设置阈值上限,LTR设置阈值下限。
  • 4个通道顺序寄存器(定序器)ADC_SQRx/JSQR(Sequence Register),用于设置常规/注入通道的转换顺序。
  • 5个数据寄存器ADC_JDRx/DR(Data Register),用于缓存注入/常规信道的转换结果。

2.3 A/D转换模块设置

1. ADC开关控制

开启ADC需要对ADC_CR2寄存器的ADON位进行两次设置:第一次向该位写入1,给ADC上电,将ADC从断电模式唤醒;第二次向该位写1,开始转换。第二次设置需要上电后延迟一段时间(tSTAB, Stabilization time),让ADC稳定下来。

清除ADON位可以停止转换并使ADC进入断电模式。断电模式下ADC几乎无功耗(只有几微安的电流)。

2. 时钟设置

ADC的时钟ADCCLK由PCLK2分频产生,在RCC_CFGR寄存器中的ADCPRE[1:0]位配置分频系数,或用库函数RCC_ADCCLKConfig()进行配置。

3. ADC校准

STM32内置的是基于开关电容技术的逐步逼近型ADC。通过一个电容大小成等比数列的电容器阵列,来实现模拟输入信号的量化。具体原理可以参考ST的AN2834应用指南。电容器容量随温度等环境参数的变化会影响ADC的精度。因此ADC内置了自校准模式。每次ADC上电后,建议都执行一次校准,这样可以显著的提高转换精度。

校准的方法是,设置ADC_CR2寄存器中的CAL位。向该位写入1后,ADC开始自校准,校准过程中会为每个电容器计算出一个误差校正码。在后续的A/D转换过程中,可以用这个校正码消除每个电容器所带来的误差。校准完成后,CAL位会被硬件置0,因此启动校准后可以通过检测该位是否归0来判断校准是否结束。校准完成后,校正码会被存入ADC_DR寄存器中,仅供查看,校准是自动的,无需用户使用此校正码去作主动校正。

注意:

  • ADC校准前,ADC必须上电(ADON=1)超过两个ADC时钟周期。可以读一次ADON,作为延时。
  • 环境温度变化较大情况下,数据采集前如果时间允许,也可以执行一下校准。

4. 数据对齐

ADC是12位的,而DR寄存器中提供了16位的存储空间。这就涉及到数据存入时的对齐问题,可以左对齐,也可以右对齐。对齐方式通过ADC_CR2寄存器的ALIGN位来选择。

如图3所示,常规通道和注入通道不同对齐方式下DR寄存器中数据位的意义不同。对常规通道,对齐后扩展位补0。左对齐时低12位为有效位,右对齐时高12位有效。

注入通道允许用户在ADC_JOFRx寄存器中设置偏移量,注入通道的DR中存入的是减掉偏移量的数值。这样就有可能会出现负值。因此,左对齐时,扩展位(高四位,SEXT)作为符号位;右对齐时,高13位为有效位,最高位为符号位,左侧的3个扩展位补0。符号位为1,则表明是负数。

数据的对其方式关系到转换数据的解析,因此必须事先约定,修改程序时也要尤其注意,避免出错。

5. 采样时间管理

ADC对模拟电压的量化可分为三个阶段:第一个阶段为采样阶段,此阶段ADC中的各个电容器阵列与模拟信号输入端(VIN)连通,进行充电;第二个阶段为保持阶段,内部电容器阵列与VIN断开,电压保持在电容器两端;第三个阶段为量化阶段,电容器通过开关依次与参考电压VREF比较,1个时钟周期比较1位。量化阶段的时间是确定的,保持阶段的时间主要是开关切换的等待时间(0.5周期),采样时间可以根据需要通过编程确定。

一次转换所需的时间可以表示为:Tconv = Tsampling + 12.5cycles

采样时间通过ADC_SMPRx配置,最短1.5cycles,最长239.5cycles。当ADCCLK=14MHz时,若Tconv=1.5+12.5=14cycles=1μs。ADC的采样速度常用sps(sample per second)来度量,1μs的转换时间相当于采样速度为1Msps。理论上来说,ADCCLK频率越高,采样时间越短,转换速度越快。但ADCCLK的最高频率是受限制的,最高采样速度与ADC转换原理和硬件参数有关,最高工作频率和采样速度是ADC的关键指标。往往精度越高(位数越多/采样过程越复杂),采样速度越低。对于高频模拟信号,ADC的转换速度至少要满足采样定理的要求,即采样频率不小于信号最高频率的2倍。

说明 :

  • ADCCLK最高14MHz,而ADC预分频系数只能是2/4/6/8,所以PCLK2=56MHz,4分频产生14MHz。如果PCLK2=72MHz,则只能选6分频,ADCCLK只有12MHz。这就是为什么手册上PCLK=56MHz时,转换时间1μs,而72MHz时反而需要1.17μs。
  • 要能识别0.5个ADCCLK周期,控制ADC采样的时钟频率至少是ADCCLK的2倍,所以ADC的预分频系数最小是2。
  • 采样时间相当于用一个窗口在时域曲线上取样,采样时间越长(窗口越宽),时间分辨率越差,但信噪比越高。往往需要根据信号情况经过实验测试,找到最佳的参数。

2.4 输入通道管理

本小节回答三个问题:多个模拟通道需要转换时,转换顺序是如何确定的?如何将一个通道设为常规通道或注入通道?注入通道是如何注入的?

1. 通道排序与分类

ADC虽然可以支持多达18个通道,但同一时刻却只能有一个通道处于转换状态。多个通道需要转换时,需要按一定的次序逐个转换各个通道。这就涉及到一个扫描顺序问题。STM32的ADC提供了4个SQR寄存器来管理通道顺序(参加图2),常称为定序器,其中3个ADC_SQRx寄存器是常规通道定序器,1个ADC_JSQR寄存器是注入通道定序器。每个序号占5个位(18个通道,需要5个位),只需将通道编号设置到定序器中,扫描或间断模式下会按照顺序进行扫描,依次执行其中安排的通道的转换。转换序列的长度(通道数)在L/JL位设置。转换顺序的设置是任意的,比如可以是3,8,2,2,0,2,2,15。

编号设置到ADC_JSQR中的通道即为注入通道。而编号设置入ADC_SQRx的通道为常规通道。所以,定序器不仅管理通道转换顺序,还管理通道的分类。

注意:

  • 常规通道序列从SQ1开始计数,即低编号优先,闲置高编号。例如,L=0010时,转换顺序是SQ1>>SQ2>>SQ3;L=0000时,ADC只转换SQ1。
  • 注入通道序列从JSQ(4-JL)开始计数,即低编号优先,闲置低编号。例如,如果JL=10,转换顺序是JSQ2>>JSQ3>>JSQ4;如果JL=01,转换顺序是JSQ3>>JSQ4;如果JL=00,ADC只转换JSQ4。

2. 注入通道管理

注入通道的转换有两种方式:自动注入和触发注入。

1)自动注入

将CR1寄存器的JAUTO位置1,开启自动注入。这种情况下,在常规组的通道序列转换完成后,会自动转换注入组的通道序列。采用自动注入,可以通过配置ADC_SQRx和ADC_JSQR寄存器,使转换序列长度高达20(=16+4)。

自动注入模式下,必须禁用注入通道的外部触发,即ADC_CR2寄存器的JEXTTRIG位必须置0。

如果CONT位和JAUTO为同时为1,注入通道转换完成后会继续下一轮常规通道的扫描。但两组通道切换期间,会自动插入一定的延迟。如果ADC时钟预分频系数在4-8之间,则插入1个ADC时钟周期的延迟。如果预分频系数是2,则会插入2个ADC时钟周期的延迟。

2)触发注入

启用触发注入前,需要先清除CR1寄存器中JAUTO位并开启SCAN位。在常规通道序列的转换过程中,如果触发了注入转换,当前通道的转换会立刻停止并复位。开始扫描转换注入通道序列,扫描一遍后,会返回原来的常规通道序列,从被打断的通道开始继续转换。

可见,注入通道有更高的优先级,可以“注入”常规通道序列的转换过程中。

注意:

使用触发注入时,触发事件的间隔必须大于注入序列的执行周期。直白点儿说,注入序列执行过程中不能重复注入。

2.5 工作模式选择

1. 单次转换模式

顾名思义,单次转换模式下ADC只执行一次转换,转换完成即停止工作。通过将ADC_CR2寄存器的CONT位置0,即可将ADC设置为单次转换模式。可以通过设置ADC_CR2寄存器的ADON位(常规通道),或通过外部触发(常规/注入通道)来启动转换。

常规(注入)通道转换结束后:数据被存入ADC_DR(JDRx)寄存器;EOC(JEOC)位被置1;如果设置了EOCIE(JEOCIE)位,会触发相应的中断。

2. 连续转换模式

将ADC_CR2寄存器的CONT位置1,即可开启ADC连续转换模式。连续模式下,一次触发即可连续转换。可通过设置CR2寄存器的ADON位或外部触发来启动转换。

每次常规(注入)通道转换结束后:数据被存入ADC_DR(JDRx)寄存器;EOC(JEOC)位被置1;如果设置了EOCIE(JEOCIE)位,会触发相应的中断。

注意:

SR寄存器中的状态位是由硬件设置、软件清除的。在程序中通过读取状态位判断转换是否结束,转换结束后才能读取数据。常规通道在读取ADC_DR时会自动复位EOC,但注入通道在下次转换前必须在软件中主动清除相关状态位,否则可能会导致转换未结束而读数,导致数据错误。

3. 扫描模式

前述单次转换和连续转换都是针对单个通道的。多通道时需要用扫描模式。通过将ADC_CR1寄存器的SCAN位置1,即可将ADC设置为扫描模式。扫描模式下,如果CONT=0,ADC按照定序器(ADC_SQRx或ADC_JSQR)中设置的顺序,执行单次扫描。一个信道转换完成,会自动转换下一个信道;如果CONT=1,则会执行连续扫描,即扫描完最后一个通道后,会从第一个通道开始继续扫描。

对常规通道,由于需要共用同一个DR寄存器,扫描模式下必须启用DMA,即置CR2寄存器中的DMA=1。每次ADC_DR寄存器中的数据更新后,由DMA控制器将转换后的数据转移到SRAM中。

每个注入通道有自己单独的JDR寄存器,因此转换后的数据会被存到ADC_JDRx寄存器中。

4. 间断模式

间断模式就是触发一次执行一次转换。与单次转换模式的区别在于,间断模式不限于一个通道,还是在一个预设的通道序列中扫描。每次触发会扫描预设序列中的下一个通道或下一组通道。

1)常规组

置ADC_CR1寄存器的DISCEN=1,即可将ADC设置为间断模式。在此模式下,ADC_SQRx寄存器中预设的转换序列会被分成长度为n(n<=8)的多个子序列。n的值在CR1寄存器中的DISCNUM[2:0]位设置。序列的总长度在ADC_SQR1寄存器的L[3:0]位设置。

一次外部触发,ADC只扫描一个子序列,子序列中的最后一个通道转换完毕,ADC停止转换。再次触发,则扫描下一个子序列。直至所有子序列均被转换。子序列中每个通道转换完成仍会产生EOC事件。

间断模式相当于把一个大组分成若干个可独立触发的小组,每个小组转换完成后可以进行必要的处理,然后再触发下一小组。

例如:若n=3,定序器中的通道号为0,1,2,3,4,7,9,10,即序列长度为8。连续触发转换的通道如下:

第一次触发:转换0,1,2;

第二次触发,转换3,6,7;

第三次触发,转换9,10;

第四次触发,转换0,1,2;

…….

注意:

  • 序列总长不能被n整除时,最后一个子序列长度会小于n。第三次触发转换的不是9,10,0,子序列不会出现rollover,而是有始有终,从头再来。

2)注入组

对注入通道序列也可以开启间断模式,对应的使能位为CR1寄存器的JDISCEN位。由于注入通道至多才4个,所以没必要划分子序列,而是每次外部触发依次转换定序器中的下一个1个通道。序列的长度在ADC_JSQR寄存器的JL[1:0]位设置。

例如:若序列长度为3,待转换序列中的通道为1,3,2。连续触发转换的通道如下:

第一次触发:转换1;

第二次触发:转换3;

第三次触发:转换2;

第四次触发:转换1;

……..

注意:自动注入和间断模式不兼容;常规组和注入组不能同时启用间断模式。

2.6 触发方式管理

ADC初始化完成后,需要被触发才会开始转换。触发方式可以是内部定时器TIM事件、外部中断线EXTI上升沿事件,也可以是软件触发。

软件触发通过设置CR2寄存器的SWSTART=1或JSWSTART=1实现。

定时器触发和外部中断线触发需要设置EXTTRIG位进行使能。

常规通道和注入通道的触发方式分别通过ADC_CR2寄存器的EXTSEL[2:0]和JEXTSEL[2:0]来设置。3个ADC的触发方式如图4所示

2.7 ADC中断

1. 中断事件和相关位

ADC相关的中断事件只有3个,分别是常规通道转换结束(EOC)、注入通道转换结束(JEOC)和模拟看门狗(AWD)事件。事件发生时硬件会设置SR寄存器中的相应标志位。通过设置CR1寄存器中的EOCIE、JEOCIE和AWDIE位开启对应的中断。

2. 模拟看门狗

模拟看门狗(AWD,Analog Watchdog)用来监控转换后的模拟电压,如果超过了预设的上下限,则会设置ADC_SR寄存器中的AWD位,如果开启了CR1寄存器中的AWDIE,则会产生中断。

阈值上限和下限分别由ADC_HTR和ADC_LTR寄存器中的低12位设置,转换结果与阈值的比较是在对齐之前进行的,因此与数据对齐方式的设置无关。

常规通道和注入通道的模拟看门狗可以分别通过CR1寄存器的AWDEN和JAWDEN位来开启或关闭,如果AWDSGL=1(AWD single),则只开启单个通道的AWD,否则开启所有通道AWD。AWDSGL=1时,开启AWD的通道由AWDCH[4:0]来设置。

2.8 其他功能

1. 双ADC模式

顾名思义,双ADC模式就是同时使用两个ADC。前提是MCU要有至少2个ADC。在双ADC模式下,ADC1为Master,ADC2为Slave。双ADC模式下,常规通道组共有ADC1_DR,高位16位存ADC2的转换数据,低16位存ADC1的转换数据,这样可经一次32bit DMA请求转走。触发可以为交替触发或同步触发。双ADC模式通过ADC1的CR1寄存器的DUALMOD[2:0]位进行设置。通过该位可以配置出9种模式:

1) 0000 – 独立模式

在此模式下,同步信号被旁路,两个ADC独立工作。

2) 0101 – 注入同步

在此模式下,两组注入通道被同步转换。触发源来自ADC1。转换数据分别存入各自的ADC_JDRx寄存器。在所有注入通道都转换完毕后,会触发中断JEOC中断(如果两个ADC中有任一个开启了JEOCIE)。

注意:

  • 不要在两个ADC中同时转换同一个通道,或者说两个ADC中转换同一通道的采样时间能重叠;
  • 两个ADC中同时取样的通道的取样时间必须完全一致。

3) 0110 – 常规同步

在此模式下,两组常规通道被同步转换。触发源来自ADC1。转换数据被存入ADC1_DR,高16位存ADC2的数据(DMA自动搬运),低16位存ADC1的数据。转换期间的EOC事件会发出32bit DMA请求,将ADC1_DR中的数据转入SRAM。当两个序列中的所有通道都转换完成时,会产生EOC中断(如果两个ADC中有任一个开启了EOCIE)。

注意:

  • 不要在两个ADC中同时转换同一个通道;
  • 两个ADC中同时取样的通道的取样时间必须完全一致。

4) 0111 – 快速交叉

此模式只能对常规通道组开启,且通常是一个通道。可以用来提高采样频率。触发源来自ADC1。触发后,ADC2立即开始转换,ADC1延迟7个ADC时钟周期后开始转换。如果两个ADC中都开启了CONT,两个ADC中所设通道序列会被连续转换。

两个ADC的转换数据均存入ADC1_DR。在ADC1触发EOC中断时,会产生32bit DMA传输请求,将ADC1_DR中的32位数据转移到SRAM。

注意:

这种模式下,最大采样周期必须小于7个ADCCLK,以避免对同一通道采样时出现重叠。

5) 1000 – 慢速交叉

此模式与快速交叉非常相似,区别在于:ADC1相对ADC2延迟14个ADCCLK开始转换,而且ADC2下一次转换也要相对ADC1的上一次转换延迟14个ADCCLK。如下图所示。

6) 0001 – 常规同步+注入同步

常规同步模式执行过程中下,被注入同步打断。注入同步转换完毕,回到常规同步模式继续转换剩下的通道。

7) 0010 – 常规同步+交替触发

常规同步模式执行过程中下,被交替触发打断。交替触发模式注入后,立即被执行。转换结束后,回到常规同步模式继续转换剩下的通道。

交替触发模式仅适用于注入通道组。触发信号来自ADC1。第一次触发,转换ADC1中的所有注入组通道;第二次触发,转换ADC2中的全部注入组通道;如此往复。

每次触发后,当前ADC中的所有注入组通道转换完毕后,可以产生JEOC中断(若已使能)。交替触发过程如下图所示。

8) 0011 – 注入同步+快速交叉

快速交叉执行过程中下,被注入同步模式打断。注入序列转换完毕,回到快速交叉模式继续转换。

9) 0100 – 注入同步+慢速交叉

慢速交叉执行过程中下,被注入同步模式打断。注入序列转换完毕,回到快速交叉模式继续转换。

2. DMA

由于常规通道共用一个数据寄存器,当转换的常规通道数大于1时,为了避免ADC_DR中存储的数据丢失,需要开启DMA。

只有常规通道在转化结束时才会产生DMA请求(注入通道不会),将ADC1_DR寄存器中的数据转移到用户设定的位置。

注意:

只有ADC1和ADC3具有DMA功能。ADC2的数据与ADC1的数据共用一个数据寄存器,可以通过双ADC模式由ADC1的DMA请求来转移数据。

3. 温度传感器

MCU内置了一个温度传感器和一个参考电源,分别在内部连到了ADC1的IN16和IN17通道。温度传感器可以用来测量MCU周边的温度(TA)。温度传感器采样时间建议设为17.1μ秒(14MHz情况下,239.5cycles),由于温度是个慢变量,不必采样太快浪费CPU资源,总之设置ADC_SMPR1的SMP16=111就可以啦。开启温度传感器需要设置TSVREFF=1,这会同时使能ADCx_IN16和IN17的转换。

温度传感器的输出电压随温度线性变化,但因工艺原因,不同MCU中温度-电压关系曲线并不一致,差别可能高达45摄氏度,因此需要根据手头MCU的情况通过实验校准一下。但这个传感器主要用来测温度变化,如果要精确的测量温度,建议外接专门的温度探头。

读取温度的步骤如下:

  • 选择ADCx_IN16输入通道。
  • 选择取样时间17.1μs。
  • 置ADC_CR2中的TSVREFE=1,将温度传感器从断电模式唤醒。
  • 设置ADON位或外部触发,启动A/D转换。
  • 从ADC_DR读取转换结果VSENSE。
  • 代入公式:T(℃) = (V25-VSENSE)/Slop + 25。其中V25是25摄氏度时读取的VSENSE。Slop是T-V曲线的斜率。这些参数从芯片出厂参数中都可以查到。如果没有,也可以自己测几个点校准一下。

三、ADC的设置

用ADC采集模拟信号的基本步骤如下:

  1. 配置系统时钟、ADC时钟;
  2. 初始化GPIO:选择通道,使能时钟,配置引脚工作模式;
  3. 如果使用中断或DMA,进行相关配置。
  4. 初始化ADC:对齐方式、工作模式(扫描/连续…)、常规/注入通道数量、转换顺序、触发方式;
  5. 根据需要配置AWD或多ADC模式;
  6. 校准ADC,选择转换方式(轮询、中断、DMA),然后启动ADC进行转换。
  7. 编写应用代码:主函数和中断处理函数。

实例:

用ADC1的IN10(PC0)和IN11(PC1)作为常规通道,连续采集3.3V电源的电压和一个热敏电阻两端的电压。开启SCAN和EOCIE,中断产生后将数据经USART1串口发送到上位机(自己开发的计算机中的MATLAB APP),用于实时绘图。

用内置的温度传感器作为注入通道。开启外部触发和JEOCIE。在外部触发(按下KEY_UP)下采集1次温度数据,在中断产生后经USART1发送到上位机。用于MATLAB APP上温度信息的显示。

3.1 寄存器操作

每个外设/功能模块都有一个类似图2所示的寄存器地址映射表。寄存器操作编程过程中,可以对着涉及到的外设/功能(GPIO、RCC、ADC等)的寄存器表,从上到下,按需依次设置寄存器的功能位即可。可以用Excle结合VBA编程来管理寄存器表,例如根据配置,自动生成Reset Mask和Set Mask。

为了增加程序的模块化,建议将外设/功能的配置或初始化划分为独立的函数,放入以外设/功能命名的.c和.h文件中,如usart.c/h、adc.c/h、gpio.c/h等。函数设计要各司其职,避免穿越。例如,如ADC相关的RCC和GPIO设置归入adc.c中的ADC1_Init函数;而LED、按键等通用GPIO归入gpio.c中的GPIO_Init。通用的函数也可以直接放入main.c中。STM32CubeMX/IDE中基本就是这么管理代码的,值得学习。

/* main.c */
/* adc.c */
void HQ_ADC1_Init(void){
	RCC->APB2ENR |= 0x00000210; //ADC1EN/IOPCEN
	GPIOC->CRL &= 0xFFFFFF00;//PC0/PC1模拟输入
	RCC->APB2RSTR |= 0x01 << 9; //复位ADC1
	RCC->APB2RSTR &= ~(0x01 << 9); //停止复位ADC1
    // 设置ADC预分频系数,ADCCLK不能超过14MHz
    RCC->CFGR &= ~(0x03 << 14); //清除ADCPRE
	RCC->CFGR |= (0x02 << 14); //ADCPRE=10,即ADCCLK = PCLK2/6
    //ADC1->CR1 &= ~(0x01 << 8); //SCAN = 0
	ADC1->CR1 |= (0x01 << 8); //SCAN = 1
	ADC1->CR2 &= ~(0x01 << 0); //ADON=0
	ADC1->CR2 |= (0x01 << 0); //ADON=1, 唤醒ADC,再次写入1才开始转换
	ADC1->CR2 &= ~(0x01 << 1); //CONT=0
    //ADC1->CR2 |= (0x01<<1); //CONT=1
	ADC1->CR2 &= ~(0x01 << 8); //DMA=0
    //ADC1->CR2 |= (0x01<<8); //DMA=1
	ADC1->CR2 &= ~(0x01 << 11); //右对齐
    //ADC1->CR2 &= ~(0x07<<12); //清除JEXTSEL
	ADC1->CR2 |= (0x07 << 12); //JEXTSEL=111,软件启动
    //ADC1->CR2 &= ~(0x01<<15); //JEXTTRIG = 0
	ADC1->CR2 |= (0x01 << 15); //JEXTTRIG = 1
    //ADC1->CR2 &= ~(0x07<<17); //清除 EXTSE
	ADC1->CR2 |= (0x07 << 17); // EXTSEL=111,软件启动
	ADC1->CR2 &= ~(0x01 << 20); // EXTTRIG = 0
    //ADC1->CR2 |= (0x01<<20); // EXTTRIG = 1

	//设置常规/注入通道数
	ADC1->SQR1 = 0x0; //复位SQR1
	ADC1->SQR2 = 0x0; //复位JQR2
	ADC1->SQR3 = 0x0; //复位SQR3
	ADC1->JSQR = 0x0; //复位JSQR
    //ADC1->SQR1 &= ~(0X0F<<20); //清除L[3:0]
	ADC1->SQR1 |=  (0x01 << 20); //L[3,0]=0001,2个转换在规则序列中
    //ADC1->SQR3 &= 0xFFFFFC00;//清除SQ1,SQ2
	ADC1->SQR3 |= 0x0000016A;//SQ1=10,SQ2=11

    //ADC1->JSQR &= ~(0x03<<20); //清除JL[1:0]
	ADC1->JSQR |=  (0x01 << 20); //JL=00,转换JSQ4,JL=01,转换JSQ3<<JSQ4...
	ADC1->JSQR |= 0x11 << 15; // IN17,JSQ4=10001
	ADC1->JSQR |= 0x10 << 10; // IN16,JSQ3=10000

	//设置常规通道/注入的采样时间
	ADC1->SMPR1 &= ~(0x07 << 0); //清除SMP10[2:0],即IN10采样1.5cycles
	ADC1->SMPR1 &= ~(0x07 << 3); //清除SMP11[2:0],即IN11采样1.5cycles
	ADC1->SMPR1 &= ~(0x07 << 18); //清除SMP16
	ADC1->SMPR1 |=  (0x07 << 18); //设置SMP16位111, 即235.9cycles
	ADC1->SMPR1 &= ~(0x07 << 21); //清除SMP17
	ADC1->SMPR1 |=  (0x07 << 21); //设置SMP17位111, 即235.9cycles

	ADC1->CR2 |= 0x01 << 23; //开启TSVREFE
	ADC1->CR2 |= 0x01 << 3; //RSTCL置1,初始化校准寄存器。软件设置,硬件清除
	while(ADC1->CR2 & 0x01 << 3); //等待硬件置0
	ADC1->CR2 |= 0x01 << 2; //CAL置1,开始校准。软件设置,硬件清除
	while(ADC1->CR2 & 0x01 << 2); //等待校准结束
}

/**
 * @概述	获取ADC内部温度传感器和参考电压的值
 * @说明	ADC1的IN16和IN17通道被配置位注入通道,分别位于JSQR3和JSQR4
 * @输入	T:双精度指针,返回内部温度传感器的温度 [T]
 * 		V:双精度指针,返回VREFINIT的值 [V]
 * 		avtimes: 采样平均次数
 * @输出	无。通过指针直接返回
 */
void HQ_ADC_GetTemVref(double* T, double* V, u8 avtimes) {
	u8 t;
	u16 T0x = 0, V0x = 0;
	for (t = 0; t < avtimes; t++) {
		ADC1->CR2 |= 0x01 << 21; //启动注入转换通道
		while(!(ADC1->SR & 0x01 << 2)); //等待转换结束
		V0x += ADC1->JDR2;
		T0x += ADC1->JDR1;//返回转换值
		ADC1->SR &= ~(0x0E); //清除JSTART,JEOC,EOC状态
		while((ADC1->SR & 0x01 << 2)); //等待JEOC清除成功
		if (t >= 0x1) { //取平均
V0x >>= 1;
T0x >>= 1;
		}
	}
	*T = (1.43 - (double)(T0x) * (3.3 / 4095)) / 0.0043 + 25;
	*V = (double)(V0x) * (3.3 / 4095);
}

3.2 HAL库函数

HAL库中与ADC相关的函数在stm32f1xx_hal_adc.c和stm32f1xx_hal_adc_ex.c中定义,二者相关的头文件分别为stm32f1xx_hal_adc.h和stm32f1xx_hal_adc_ex.h。前者定义ADC的通用API(HAL_ADC_*),而后者则是具体型号专有的API(HAL_ADCEx_*)。

HAL库API中,有两类函数:宏函数和普通函数。宏函数在.h文件中定义,普通函数在.c文件中定义。宏函数可为两类:库函数(__HAL_PPP*)和私有函数(PPP_*,IS_PPP*)。普通函数也分为两类:库函数(HAL_PPP*)和私有函数(PPP_*)。库函数是供用户代码调用的,而私有函数主要是供库函数调用的。

ADC相关的库函数分类概述如下:

1. ADC初始化和复位函数

包括ADC及其相关外设(时钟、IO等)的初始化和复位函数。

  • HAL_ADC_Init()

初始化ADC外设和常规组。与其他外设一样,配置ADC必须先使能其时钟。HAL库用HAL_PPP_MspInit()来完成外设的初始化(Msp,MCU specific peripheral),包括时钟使能、IO模式配置、中断初始化等。在HAL_PPP_Init()中会首先调用HAL_PPP_MspInit(),然后再根据输入的外设句柄中的参数进行初始化。

  • HAL_ADC_DeInit()

HAL_PPP_DeInit()用来复位外设,即将与外设相关的寄存器恢复复位值。如果在外设运行起来后需要修改其Msp,必须先调用DeInit()函数,然后再调用Init()函数用修改后的Msp重新初始化。

  • HAL_ADC_MspInit()

初始化MCU相关的外设参数。HAL_PPP_Init()是通用函数,不同的MCU外设的参数不一定一样,例如外设对应的IO引脚。HAL_PPP_MspInit()是一个weak函数,用来将MCU相关的参数隔离出去,由用户根据实践情况完成时钟使能、IO配置、中断配置等初始化。

  • HAL_ADC_MspDeInit()

这也是个weak函数,用来将Msp参数复位。

2. 输入输出操作函数

包括A/D转换的启动、校准、停止,转换状态检查,获取转换结果相关的函数,以及中断处理和回调函数。ADC支持3种模式:轮询、中断和DMA。

  • HAL_ADC_Start()

HAL_ADC_Start_IT()

HAL_ADC_Start_DMA()

启动常规组通道转换。调用ADC_Enable()使能ADC,根据配置参数设置相关寄存器状态,触发常规组的转换。无后缀的函数采用轮询方式,*_IT为中断方式,*_DMA为DMA方式。

HAL_ADCEx_InjectedStart()

HAL_ADCEx_InjectedStart_IT()

启动注入组通道转换。注入组每个通道有单独的数据寄存器JDRx,因此无需DMA。

HAL_ADCEx_MultiModeStart_DMA()

启动双ADC模式转换。仅适用于有多个ADC的MCU,其中ADC1作为Master另一个作为Slave。Slave的常规组必须设置为软件触发。Slave可通过HAL_ADC_Start()预先使能。这个函数中的hadc为Master的句柄。Slave转换后的数据通过DMA自动导到Master的DR寄存器高16位。

  • HAL_ADCEx_Calibration_Start()

启动ADC自校准。校准前ADC必须处于Disable状态,即必须在调用HAL_ADC_Start*前或调用HAL_ADC_Stop*后进行校准。一般在ADC_Init函数中进行校准。

  • HAL_ADC_PollForConversion()

检查常规组转换是否完成。对常规通道的单次转换(扫描关闭或即使开启但只有一个待转换通道),根据EOC状态判断是否转换完成;对序列转换(扫描开启且带转换通达>=2),EOC仅在序列转换结束才被设置,通道之间无EOC,该函数中根据通道转换周期(采样周期+12.5)和ADCCLK频率来计算单个通道转换完成的时间。等待所有通道中最长的转换时间,如果不超时即认为转换完成并设置hadc->State的状态为HAL_ADC_STATE_REG_EOC。调用该函数后可以接着查询hadc->State来判断是否可以读取转换数据了。如果超时,函数返回HAL_TIMEOUT,运行结束返回HAL_OK。该函数不适用于开启DMA的ADC模式,DMA会复位EOC标志位,只是无法每个新的进行poll。

HAL_ADC_PollForEvent()

检查是否有AWD事件。如果AWD检测到电平超界,会将hadc->State设置为HAL_ADC_STATE_AWD1。

HAL_ADCEx_InjectedPollForConversion()

检查注入组转换是否完成。

  • HAL_ADC_Stop()

HAL_ADC_Stop_IT()

HAL_ADC_Stop_DMA()

停止常规组转换。与Start函数对应的Stop函数。调用
ADC_ConversionStop_Disable()停止转换并关闭ADC。中断版中还会复位CR1寄存器的EOCIE位,DMA版中复位CR2寄存器的DMA位。

HAL_ADCEx_InjectedStop()

HAL_ADCEx_InjectedStop_IT()

停止注入组转换。注入组不支持DMA。

HAL_ADCEx_MultiModeStop_DMA()

停止双DMA转换。hadc为Master ADC的句柄。

  • HAL_ADC_GetValue()

返回常规通道转换结果,即ADC_DR寄存器中的值。

HAL_ADCEx_InjectedGetValue()

返回注入通道转换结果,即ADC_JDRx寄存器中的值

HAL_ADCEx_MultiModeGetValue()

返回双ADC转换结果,即主ADC的ADC_DR中的值,高16位存Slave ADC的结果。

  • HAL_ADC_IRQHandler()

中断请求处理函数。检查CR1寄存器的EOCIE/JEOCIE/AWDIE的状态,然后分别调用HAL_ADC_ConvCpltCallback /
HAL_ADCEx_InjectedConvCpltCallback / HAL_ADC_LevelOutOfWindowCallback 函数响应中断事件。该函数管理的是ADC事件触发的中断请求,并调用相应的回调函数。DMA模式下的DMA事件触发的中断,回调函数不经IRQHandler,而是在*Start_DMA中管理。

  • HAL_ADC_ConvCpltCallback()

常规组转换结束回调函数。weak函数需要用户自己实现。

_HAL_ADC_ConvHalfCpltCallback()

DMA模式转换一半回调函数。这实际上是由DMA的事件触发的中断,所以只有在开启DMA的模式下才会调用该函数来处理回调。对ADC而言是在HAL_ADC_Start_DMA和
HAL_ADCEx_MultiModeStart_DMA中管理该回调函数,因为不是ADC的中断引起的回调,因此不在HAL_ADC_IRQHandler()中管理。

_HAL_ADCEx_InjectedConvCpltCallback()

注入组转换结束回调函数。weak函数需要用户自己实现

_HAL_ADC_LevelOutOfWindowCallback()

模拟看门狗事件回调函数。weak函数,需要用户自己实现。

_HAL_ADC_ErrorCallback()

ADC错误回调函数。被ADC_DMAError()调用,处理DMA过程中的错误。这是个weak函数,需要用户自己实现。

3. 控制函数

包括常规组/注入组通道配置和模拟看门狗配置。

  • HAL_ADC_ConfigChannel()

常规模式配置。通道参数由ADC_ChannelConfTypeDef结构体输入,包括通道号(ADC_CHANNEL_x)、排序(ADC_REGULAR_RANK_x)和采样时间(ADC_SAMPLETIME_1CYCLE_5,…)

HAL_ADCEx_InjectedConfigChannel()

注入模式配置。

HAL_ADCEx_MultiModeConfigChannel()

双ADC模式配置。multimode指定模式。该函数中基于Master ADC的句柄,为Slave ADC创建一个句柄,然后设置CR1寄存器的DUALMOD[3:0]功能位。

  • HAL_ADC_AnalogWDGConfig()

配置模拟看门狗。包括模式(主要管理的是CR1寄存器的AWDEN/JAWDEN/AWDSGL3个功能位)、监控的通道(AWDCH)、中断状态(AWDIE)、电平上下限(ADC_HTR和LTR寄存器)。

4. 状态检查和错误处理函数

包括查询ADC运行状态的函数和错误处理函数。

  • HAL_ADC_GetState()

查询ADC状态。HAL库中用状态机来管理外设的运行状态。所谓状态机,就是外设句柄中的State字段,通过hadc->State可以随时查询外设的运行状态。ADC相关的状态宏在stm32f1xx_hal_adc.h中定义。

  • HAL_ADC_GetError()

查询ADC错误。与State字段相似,外设的错误用ErrorCode字段管理,通过hadc->ErrorCode可以查询错误的代码。

5. 宏库函数

  • __HAL_ADC_ENABLE() 使能ADC
  • __HAL_ADC_DISABLE() 关闭ADC
  • __HAL_ADC_ENABLE_IT() 使能指定中断
  • __HAL_ADC_DISABLE_IT() 关闭指定中断
  • __HAL_ADC_GET_IT_SOURCE() 获取指定中断源
  • __HAL_ADC_GET_FLAG() 获取指定标志位
  • __HAL_ADC_CLEAR_FLAG() 清除指定标志位
  • __HAL_ADC_RESET_HANDLE_STATE() 状态机复位(State字段)
本文来自网络,不代表品牌家电维修网立场,转载请注明出处:https://www.33x1.com/zhishi/569791.html

作者: baixiuhui1

为您推荐

联系我们

联系我们

18079759494

在线咨询: QQ交谈

邮箱: 964571095@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

返回顶部