16位的ad可以说是国产mcu的痛点,至少在廉价的单片机里面,这个真的找不到飞思卡尔的替代品。之前未使用16位ad的时候,使用的是stm32f0的单片机,因为产品需要,一直是将48m的主频超频到56m跑超速,后来因为疫情等原因,st的价格飞上天,交期还特长,无奈之下换了国产兆易创新的gd32,不得不说,对标的gde23主频直接到了72m,m0+,不用超频,正常跑高速就行。价格还便宜,不收过路费。在这一点上,国产的mcu真的很强。
现在项目需要16位的ad,一时间找不到任何国产的替代品,当然我们也把主意打到了st的头上,但是捋到stm32h7才找到16位ad,2020年的st的价格大家都清楚,如果选用这款芯片,我们的产品成本将大大增加,这已经超出了我们的预算。在之后的一番寻找中,确定了这个被恩智浦收购了多年的飞思卡尔的芯片。 mkv30,价格便宜,针对电机行业出生的mcu,在adc的处理上可谓是下足了功夫。
自带差分输入模块,支持高达16位的差分ad输入,自带硬件平均,可对输入的ad信号进行自动平均,支持低功耗和高速ad模式,可自动校准ad,自带比较器。 但是,因为很早就被收购,所以飞思卡尔的资料并不如nxp自家的产品那样详细丰富,导致开发难度很大,而且这款芯片不像k60那款,因为早期有智能车竞赛的缘故,网友分享的资料和经验很多。这款我拿到手里就很懵。本人并不是大佬,对新的单片机上手不是很容易。在开发的第一周就点了个灯,到处是坑。 下面分享我的开发过程和经验: 官网下载sdk直接pass,在有个基础工程的基础上使用mcuxpresso config tool配置adc的引脚和功能初始化。 配置引脚:
因为我需要使用两路adc的差分模式,这里配置adc0和adc1的引脚。使用porte16、porte17 、porte18 、porte19四个引脚。对应adc的adc0_dp1,adc0_dm1,adc1_dp1,adc1_dm1。软件会自动配置引脚相关配置代码。 adc配置:
配置为16位的差分ad,因为我追求最高速的adc采集,所以时钟1分频,硬件的8次平均。 adc1配置相同。 开始进入代码:
/******************************************************************************** definitions******************************************************************************/#define
demo_adc16_channel 1u#define demo_adc16_channel_group 0u#define
demo_adc16_baseaddr adc0#define demo_dmamux_baseaddr dmamux0#define
demo_dma_channel 1u#define demo_dma_adc0_source 40u#define
demo_dma_adc1_source 41u#define demo_dma_baseaddr dma0#define
adc16_result_reg_addr 0x4003b010u#define adc16_result_reg_addr1 0x40027010u//查询寄存器手册得到#define demo_dma_irq_id dma0_irqn #define demo_adc16_sample_count 8u /* the adc16 sample count. *//************************************************************************************************************************
adc0 initialization code**********************************************************************************************************************/adc16_channel_config_t
adc0_channelsconfig[1] = { { .channelnumber = 1u, //传输通道 .enabledifferentialconversion = true, //差分模式 .enableinterruptonconversioncompleted = false, //使能传输完成中断 }};const adc16_config_t adc0_config = { .referencevoltagesource = kadc16_referencevoltagesourcevref, .clocksource = 0, .enableasynchronousclock = false, .clockdivider = kadc16_clockdivider1, .resolution = kadc16_resolutionse16bit, .longsamplemode = kadc16_longsampledisabled, .enablehighspeed = true, .enablelowpower = false, .enablecontinuousconversion = false//连续的转换};const
adc16_channel_mux_mode_t adc0_muxmode = kadc16_channelmuxa;/* 硬件平均 8 */const
adc16_hardware_average_mode_t adc0_hardwareaveragemode = kadc16_hardwareaveragedisabled;void adc0_init(void) { /* initialize adc16 converter */ adc16_init(adc0_peripheral, &adc0_config); /* make sure, that software trigger is used */
adc16_enablehardwaretrigger(adc0_peripheral, false); /* configure hardware average mode */ adc16_sethardwareaverage(adc0_peripheral, adc0_hardwareaveragemode); /* configure channel multiplexing mode */
adc16_setchannelmuxmode(adc0_peripheral, adc0_muxmode); /* initialize channel */
adc16_setchannelconfig(adc0_peripheral, 0u, &adc0_channelsconfig[0]); /* perform auto calibration */ adc16_doautocalibration(adc0_peripheral); /* enable dma. */ adc16_enabledma
(adc0_peripheral, false);}/************************************************************************************************************************ adc1 initialization code**********************************************************************************************************************/adc16_channel_config_t adc1_channelsconfig[1] = { { .channelnumber = 2u, .enabledifferentialconversion = true, //差分模式
.enableinterruptonconversioncompleted = false, }};const adc16_config_t adc1_config = { .referencevoltagesource = kadc16_referencevoltagesourcevref, .clocksource = 0, .enableasynchronousclock = false, .clockdivider = kadc16_clockdivider1, .resolution = kadc16_resolutionse16bit, .longsamplemode = kadc16_longsampledisabled, .enablehighspeed = true, .enablelowpower = false, .enablecontinuousconversion = false//连续的转换};const
adc16_channel_mux_mode_t adc1_muxmode = kadc16_channelmuxa;const
adc16_hardware_average_mode_t adc1_hardwareaveragemode = kadc16_hardwareaveragedisabled;void adc1_init(void) {// enableirq(adc0_irqn); /* 初始化adc16转换器 */
adc16_init(adc1_peripheral, &adc1_config); /* 不使用软件触发器 */
adc16_enablehardwaretrigger(adc1_peripheral, false); /* 配置硬件平均模式 */ adc16_sethardwareaverage(adc1_peripheral, adc1_hardwareaveragemode); /* 配置信道多路复用模式 */ adc16_setchannelmuxmode(adc1_peripheral, adc1_muxmode); /* 初始化通道 */
adc16_setchannelconfig(adc1_peripheral, 1u, &adc1_channelsconfig[0]); /* 自动校准 */
adc16_doautocalibration(adc1_peripheral); /* enable dma. */ adc16_enabledma(adc1_peripheral, false); } 这里以adc0为例,传输通道设置为1,配置为差分模式,不使能传输完成中断。
adc0_config结构体中的配置主要是配置时钟和采样速度,我的配置是我能达到的最高速度。在adc0_init函数中,配置为软件触发,如果使用pdb,需要改为硬件触发,关闭了硬件平均。 当我们需要获取adc的数据时,需要以下代码。
adc16_channel_config_t adc16channelconfigstruct; adc16channelconfigstruct.channelnumber = 1; //adc通道 adc16channelconfigstruct.channelnumber = 2;
adc16channelconfigstruct.enabledifferentialconversion = true;//使能差分
adc16channelconfigstruct.enableinterruptonconversioncompleted = false;//失能中断
adc16_setchannelconfig(adc1, 0u, &adc16channelconfigstruct); adc16_setchannelconfig(adc0, 0u, &adc16channelconfigstruct); while (0u == (kadc16_channelconversiondoneflag &
adc16_getchannelstatusflags(adc1, 0u))); adc_value0 = adc16_getchannelconversionvalue(adc0, 0u); adc_value1 = adc16_getchannelconversionvalue(adc1, 0u);
可以将上述代码添加进主循环,在需要ad值时便可以直接读取adc_value0和adc_value1的值便可,可以包装成一个函数,需要时调用即可,执行一次该代码大约需要3us。如果ad的通道很多,可以使用for循环,改善代码。但是此方法占用mcu的内存,下一篇更新灵活多通道的dma采集。 要点: 这里配置为adc16位模式,但是并不是真正意义上的16位,在数据寄存器中有介绍,数据寄存器是16位,只有低15位是有效数据位,最高位为16位,所以adc的范围是0~32767,加上最高位的符号位能达到-32767~+32767.
我在这里没看手册,采集到的数据一直无法理解。
输入通道输入的是正弦波,结果串口打印出来的确是这个玩意,最后处理一下符号位解决。 上电之后会开始adc采集,adc采集完成触发dma通道1开始传输到指定缓存,dma通道1传输完成触发链接,链接dma通道2,dma通道2将adc配置传给adc配置寄存器。这样可以灵活采集各种通道,并且对资源占用较小。只要设置好配置adc的数组,剩下的dma就会处理.dma配置:
void edma_configuration(void){ edma_config_t userconfig; /* 配置 dmamux */ dmamux_init(dmamux); /* 通道ch1初始化 */ dmamux_setsource(dmamux, 1, 40); /* map adc0 source to channel 1 */
dmamux_enablechannel(dmamux, 1); /* 通道ch2初始化 */ dmamux_setsource(dmamux, 2, 41);/* map adc1 source to channel 2 */ dmamux_enablechannel(dmamux, 2); /* 获取edma默认配置结构 */
edma_getdefaultconfig(&userconfig); edma_init(dma0, &userconfig); edma_createhandle(&g_edma_handle, dma0, 1); /* 设置回调 */ edma_setcallback(&g_edma_handle, edma_callback, null); /*edma传输结构配置 。设置dma通道1的adc值传到g_adc16sampledataarray*/ edma_preparetransfer(&transferconfig, (void *)adc16_result_reg_addr, sizeof(uint32_t), (void *)g_adc16sampledataarray, sizeof(uint32_t), sizeof(uint32_t), sizeof(g_adc16sampledataarray),
kedma_peripheraltomemory); edma_submittransfer(&g_edma_handle, &transferconfig); /* enable interrupt when transfer is done. */ edma_enablechannelinterrupts(demo_dma_baseaddr, demo_dma_channel, kedma_majorinterruptenable);#if defined(fsl_feature_edma_asynchro_request_channel_count) &&
fsl_feature_edma_asynchro_request_channel_count /* enable async dma request. */
edma_enableasyncrequest(demo_dma_baseaddr, demo_dma_channel, true);#endif /*
fsl_feature_edma_asynchro_request_channel_count */ /* enable transfer. */
edma_starttransfer(&g_edma_handle); //将dma通道1链接到通道0 edma_setchannellink(dma0, 1,
kedma_minorlink, 2); edma_setchannellink(dma0, 1,
kedma_majorlink,2); //*********************************************************************************************/
edma_createhandle(&dma_ch2_handle, dma0, 2); edma_setcallback(&dma_ch2_handle, edma_callback1, null); /* 设置回调 */ edma_preparetransfer(&g_transferconfig, (void *)adc16_result_reg_addr1, sizeof(uint32_t), (void *)g_adc16sampledataarray1, sizeof(uint32_t), sizeof(uint32_t), sizeof(g_adc16sampledataarray1), kedma_peripheraltomemory); edma_submittransfer(&dma_ch2_handle, &g_transferconfig); //传输完后修正通道 dma0-》tcd[1].dlast_sga = -1* sizeof(g_adc16sampledataarray); dma0-》tcd[2].dlast_sga = -1* sizeof(g_adc16sampledataarray1); /* 当传输完成时启用中断。 */ edma_enablechannelinterrupts(demo_dma_baseaddr, 2, kedma_majorinterruptenable);#if defined(fsl_feature_edma_asynchro_request_channel_count) &&
fsl_feature_edma_asynchro_request_channel_count// /* 启用异步dma请求 */
edma_enableasyncrequest(demo_dma_baseaddr, 2, true);#endif /* fsl_feature_edma_asynchro_request_channel_count */ /* 使能数据传输 */ edma_starttransfer(&dma_ch2_handle); }
dam 通道1和通道2的callback函数。 因为通道2是通过通道一链接触发的,所以在通道1的回调函数里面就不用再调用edma_starttransfer()函数了。 此处注意将adc的采样模式改为连续模式。
static void edma_callback(edma_handle_t *handle, void *userdata, bool transferdone, uint32_t tcds){ edma_starttransfer(&g_edma_handle); g_transfer_done = false; if (transferdone) { g_transfer_done = true; }} static void edma_callback1(edma_handle_t *handle, void *userdata, bool transferdone, uint32_t tcds){ g_transfer_done1 = false; if (transferdone) { g_transfer_done1 = true; }}
至此adc的dma就完成了,adc会一直采集并通过dma传输到g_adc16sampledataarray[]和g_adc16sampledataarray1[]两个数组中,需要时可以直接取值。我在使用adc的dma连续采样时遇到一个问题,因为连续采样会触发callback函数,此过程会触发edma中断,容易打断原来代码的进程,如在高速应用中使用需注意。
芯片的入门环境搭建
该芯片的入门环境搭建,内容主要是官网获取asdk,这个芯片我不知道是因为用的特别少,还是没公开开发经验,很难找到相关资料,只有在nxp社区能找到一点资料,代理商也是只负责销售,技术问题一概不管。在一顿乱搞之后搭建完一些工具之后,发现官方的sdk在我的板子上根本跑不起来,但是还好nxp论坛还有一个管理员提供一些支持,让我能一步步走下来。那就开始点一个灯吧。 无论是下载资料还是论坛讨论都必须注册nxp账号,注册这里不谈,跳过。
在此链接下找到mcuxpresso config tools 软件,然后下载。
下载完成自己安装,此软件可自行配置工程,相当于st的stm32cubemx,可以方便配置时钟外设,我们只用专注于写逻辑便好,因为我是自己画的板子,搭建的工程无法使用,只用它配置外设。 相同的页面继续下载一个sdk,mcuxpresso software development kit (sdk)
在搜索框输入芯片名称,会弹出相应开发板或芯片,我是自己打的板子,选择芯片
选择sdk版本
点击进入sdk
直接下载就好啦,因为我没有梯子,下载特别慢,用其他浏览器会下载失败,推介使用谷歌浏览器。
之后下载keil的mdk包,这个自行去官网下载。
原文标题:国产16位mcu的痛点,可以用这款物美价廉产品(附完整开发过程)
文章出处:【微信公众号:嵌入式arm】欢迎添加关注!文章转载请注明出处。
苹果或为iPhone推出磁性连接的无线电池组MagSafe
详细剖析在嵌入式系统开发中选择适合的MCU平台
冷热冲击试验箱是如何制冷的?
华为P40 Pro相机参数曝光搭载了5200万像素主摄像头支持3倍光学变焦
南京海立电池
16位AD是国产MCU的痛点
智能合约目前还只是停留在了合约的阶段还有很长的路要走
华为提前发布鸿蒙OS,美国谷歌安卓走着瞧
VIAVI推出首款真正的5G基站分析仪,助力大规模部署
拜登问鼎美国总统,美国科技圈迎来一片欢呼声
氮化镓充电宝和普通充电宝区别
电动汽车动力电池和充放电原理及充电桩的分类
多个GPU标准C++并行编程加速计算的优点
新一代智能型日光调控系统设计
海信将发布采用全新技术工艺生产的折叠屏的电视
高通入局PC市场,有何优势,野心能否如愿以偿
浅析汽车传动轴结构、工作原理
便携式蓝牙音箱的DIY图解
魅族与德州仪器合作生产芯片,果然自研芯片才是出路!
基于NXP i.MX8M Plus芯片的OP-Killer AI原型开发板方案