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】歡迎添加關注!文章轉載請注明出處。
責任編輯:haq
-
芯片
+關注
關注
454文章
50430瀏覽量
421874 -
單片機
+關注
關注
6032文章
44521瀏覽量
633105 -
mcu
+關注
關注
146文章
17002瀏覽量
350328
原文標題:國產16位MCU的痛點,可以用這款物美價廉產品(附完整開發過程)
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論