با سلام، در این قسمت از آموزشها میریم سراغ واحد DMA و ADC، در این آموزش میخواهیم یک دیتای آنالوگ رو بخونیم و بدون درگیرشدن CPU از Data Register واحد ADC منتقلش کنیم به یک متغیر یا یک آرایه که بتوان از آن استفاده نمود.
خود DMA به معنای دسترسی مستقیم به حافظه هست، به این معنا که هر وقت لازم باشه دیتایی از آدرسی به آدرس دیگه بره ما میتونم از DMA استفاده بکنیم، معمولاً برای اینجور انتقالات از CPU استفاده میشه، ولی اگر حجم دیتا زیاد باشه، یا سرعت انتقال زیاد باشه، میتونه CPU رو به چالش بکشه پس مجبوریم از DMA استفاده کنیم.
حتی میتوان DMA رو طوری برنامهریزی کرد که کاملاً مستقل از CPU کار کند؛ یعنی حتی نیاز به دستور مستقیم CPU برای انتقال نیز نداشته باشد، مثلاً یک تایمر میتواند بدون درگیرکردن CPU مثلاً هر 10 میلی ثانیه یک بار اطلاعات را از جایی به جای دیگر منتقل کند.
DMA یک مبدأ دارد، یک مقصد، یک عامل تحریک شروع انتقال، یک جهت برای انتقال و سایز دیتایی که باید منتقل شود، DMA قابلیت افزایش آدرس در حافظه رو نیز دارد، مثلاً میتواند بدون درگیرشدن CPU یک آرایه رو پیمایش کند و اطلاعات آن را آپدیت کند.
اینکه بتوانیم بدون درگیرشدن CPU دیتاهایی رو از جایی به جای دیگر انتقال دهیم نیازمند DMA هست.
در این میکرو هر کانال DMAسه مد کاری دارد:
شما میتونید جهت انتقال رو عوض کنید ولی نیاز به کانفیگ مجدد دارد و نمیتواند به طور همزمان توسط یک کانال در دوجهت باشد و این کار نیز مستلزم پشتیبانی پریفرال نیز هست که ما در این میکرو چنین پریفرالی نداریم.
خب بریم سراغ واحد DMA در این میکرو:
به این دو جدول در برنامه امون بهشدت نیاز داریم، متأسفانه دیاگرام داخلی خاصی برای واحد DMA در اختیارمون قرار ندادهاند، البته لازم هم نداریم در این بخش.
مثال مشابه MEM to MEM همون تابعی هست که هممون در زیان C میشناسیم و از توابع خود زبان C هست؛ ولی اون تابع باعث اشغالشدن CPU میشه ما میتوانیم، ارائهای که میخواهیم کپی کنیم در آرایه دیگر رو با استفاده DMA بهصورت موازی و با سرعت بالا کپی کنیم و فقط CPU از شروع و پایانش اطلاع پیدا میکنه. چند نکته در مورد MEM to MEM بگم خدمتتون:
اگر فقط خواستید یک مقدار ثابت رو ست کنید داخل کل ارائه نباید آدرس آن مقدار ثابت رو افزایش بدید و فقط آدرس ارائه رو افزایش بدید مقدار سایز هم ارائه تعیین میکنه.
اگر سایز دو تا ارائه یکی نبود مقدار سایز کوچیکترین آرایه در نظر گرفته میشود.
اگر سایز ارائه داینامیک بود یا مقدار دیتا در آن هر سری تفاوت داشت، باید قبل از شروع ارسال سایز آن مجدداً تنظیم شود.
سایزهای ارائهها با دقت انتخاب کنید و اگر سایزها رو افزایش یا کاهش میدید، مطمئن شوید هیچوقت از سایز ارائه بیشتر نشود که باعث دیتاهای نادرست و در گاهی اوقات هنگ کردن میکرو میشود.
خب این در مورد MEM to MEM بود بریم سراغ MEM to Device
در این مد یک ارائه یا یک متغیر رو ست میکنیم تا دیتا رو به یک پریفرال ارسال کند.
مثلاً در مثال DAC دیدیم که کلی ارائه ۳۶۰تایی داشتیم از شکل موجهای مختلف با استفاده از DMA میتوانستیم بدون استفاده از CPU همه مقادیر رو روی DAC ست کرد، حتی میشد از CPU برای تریگ کردن انتقال استفاده نکرد و با یک تایمر این کار رو انجام داد.
و در آخر پریفرال به حافظه رو داریم که دیتا رو از یک پریفرال به حافظه انتقال میدهیم مثل مثالی که میخواهیم انجام بدهیم.
بریم سراغ راهاندازی این مثال و ببینیم هر خط از کد کارش چیه؟
اول کلاکهای واحدهای موردنیاز رو فعال میکنیم.
سپس شروع میکنیم به کانفیگ کردن هر بخش، اولازهمه GPIO رو روی مد ورودی آنالوگ قرار میدهیم، بهصورت پیشفرض روی همین مد هست. ما برای اطمینان تنظیمات رو انجام میدهیم.
سپس تایمر رو تنظیم میکنیم که DMA رو تریگ بکنه:
انتخاب این تایمر و نوع تریگ کردنش برمیگرده به لیستی که اول آموزش خدمتتون فرستادم اگر نگاه کنید تایمر دو آپدیت رو ما استفاده کردیم که ما رو به کانال 2 DMA میرسونه، پس برای این انتقال از کانال دو DMA استفاده میکنیم.
از SPI1_RX , USART3_TX,TIM1_CH1,TIM3_CH3 هم میتونید برای این آپدیت استفاده کنید.
همه کدهای تایمر در بخش تایمر توضیح داده شدند فقط خط آپدیت DMA استفاده شده که تعیین شده با استفاده از آپدیت شدن تایمر، DMA تریگ میشه و یک انتقال رو انجام میده.
حالا نوبت بخش ADC هست:
همه کدها در آموزش ADC توضیح داده شدهاند جز خط ADC_DMACMD که تعیین میکند که آیا با DMA انتقال انجام بشود یا نه که ما فعالش میکنم.
بعدش میرسیم به خود DMA:
اولازهمه سایز دیتا رو تعیین میکنیم که سایز ارائه ما هست، با استفاده از تابع sizeof() مقدارش رو به ورودی برمیگردونیم.
طرف انتقال دیتا از پریفرال به حافظه است، پس DMA_DIR_PeripheralSRC میزاریم به معنای اینکه منبع ما پریفرال هست.
با غیرفعالکردن M2M یعنی مقصد یا مبدأ ما حتماً یک پریفرال را دارد و ما با پریفرال کار داریم.
آدرس شروع پریفرال رو میدیم که میتونیم با استفاده از پوینترها به دست بیاریم.
مد رو چرخشی انتخاب میکنیم تا بهصورت چرخشی انتقال رو انجام بده اگه نرمال بزاریم باید قبل از هر انتقال یکبار تریگ بشه.
سایز دیتای انتقالی رو 16 بیتی یا HALFWORD قرار دادیم چون دیتای ADC ما 12 بیتی هست و 16 بیت براش کافی هست.
چون پریفرال ما فقط یک رجیستر تکی برای دیتا دارد و نباید روی این رجیستر حرکت کرد برای خوندن اطلاعات پس ما آدرس پریفرال رو افزایش نمیدیم.
اولویت انتقال رو اینجا روی HIGH تعیین کردیم. آدرس شروع ارائه رو بهش میدیم. سایز حافظه هم 16 بیتی در نظر میگیریم.
ولی در اینجا چون ما ارائه داریم و میخواهیم هر دیتا داخل یکی از این خونه ها قرار بگیرد پس آدرس آن را افزایشی انتخاب میکنیم که پس از هر انتقال بره به آدرس بعدی.
و در آخر DMA رو فعال میکنیم.
الان میتونیم ارائه DATA رو بخونیم بدون اینکه CPU درگیر انتقال این دیتا به ارائه شده باشه.
بقیه مدهای انتقالی هم به همین شکل هستن فقط باید مبدأ و مقصد رو تعیین کنید و با استفاده از نرمافزار یا سختافزار DMA رو تریگ کنید تا شروع به انتقال کند.
کد کامل این بخش:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | /********************************** (C) COPYRIGHT ******************************* * File Name : main.c * Author : WCH * Version : V1.0.0 * Date : 2021/06/06 * Description : Main program body. ********************************************************************************* * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. * Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics. *******************************************************************************/ /* *@Note USART Print debugging routine: USART1_Tx(PA9). This example demonstrates using USART1(PA9) as a print debug port output. */ #include "debug.h" /* Global typedef */ /* Global define */ uint16_t data[20]; /* Global Variable */ /********************************************************************* * @fn main * * @brief Main program. * * @return none */ int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init(115200); printf("SystemClk:%d\r\n",SystemCoreClock); printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() ); printf("This is printf example\r\n"); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); GPIO_InitTypeDef GPIOCFG; GPIOCFG.GPIO_Mode = GPIO_Mode_AIN; GPIOCFG.GPIO_Pin = GPIO_Pin_0; GPIO_Init(GPIOA,&GPIOCFG); ///////////////////////////////////////////////////////////BASE TIME TIMER TIM2 TIM_TimeBaseInitTypeDef TIMBCFG; TIMBCFG.TIM_ClockDivision = TIM_CKD_DIV1; TIMBCFG.TIM_CounterMode = TIM_CounterMode_Up; TIMBCFG.TIM_Period = (72000000 / 44100) - 1; // (Systemclock/PSP) -> (144 / 2) ==> (72MHz / 44100Hz) -1 TIMBCFG.TIM_Prescaler = 2 - 1; TIMBCFG.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM2, &TIMBCFG); // Enable TIM2 Update DMA Request TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); ///////////////////////////////////////////////////// ADC_InitTypeDef ADCCFG; ADCCFG.ADC_ContinuousConvMode= ENABLE; ADCCFG.ADC_DataAlign = ADC_DataAlign_Right; ADCCFG.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADCCFG.ADC_Mode = ADC_Mode_Independent; ADCCFG.ADC_NbrOfChannel = 1; ADCCFG.ADC_OutputBuffer = DISABLE; ADCCFG.ADC_Pga = ADC_Pga_1; ADCCFG.ADC_ScanConvMode = DISABLE; ADC_Init(ADC1,&ADCCFG); ADC_Cmd(ADC1,ENABLE); ADC_DMACmd(ADC1,ENABLE); ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_28Cycles5); ADC_StartCalibration(ADC1); Delay_Ms(50); ADC_SoftwareStartConvCmd(ADC1,ENABLE); ///////////////////////////////////////////////////////// DMA_InitTypeDef DMA1CFG; DMA1CFG.DMA_BufferSize =sizeof(data); DMA1CFG.DMA_DIR = DMA_DIR_PeripheralSRC; DMA1CFG.DMA_M2M = DMA_M2M_Disable; DMA1CFG.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->RDATAR; DMA1CFG.DMA_Mode = DMA_Mode_Circular; DMA1CFG.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA1CFG.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA1CFG.DMA_Priority = DMA_Priority_High; DMA1CFG.DMA_MemoryBaseAddr = (uint32_t)data; DMA1CFG.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA1CFG.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_Init(DMA1_Channel2, &DMA1CFG); // Enable DMA DMA_Cmd(DMA1_Channel2, ENABLE); //////////////////////////////////////////////////////// while(1) { for(int i =0;i<20;i++) { printf( "ADC_DATA:%04d\r\n", data[i] ); Delay_Ms(10); } Delay_Ms(100); } } |
نویسنده شو !
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.