در قسمت قبلی آموزش ساخت تایمر 32 و 64 بیتی را فرا گرفتید و در بخش دهم، با واحد ADC آشنا شدیم و در بخش پانزدهم نحوه دریافت اطلاعات از ADC توسط DMA را دیدیم. در این بخش میخواهیم کاری کنم که واحد ADC با سرعت بیشتری نمونهبرداری کند. میکروکنترلر STM32F103C8 دارای دو واحد ADC است که حداکثر سرعت نمونهبرداری هرکدام از آنها 1 Ms/s یا یکمیلیون نمونه در ثانیه است. ما میتوانیم با بهکار بردن همزمان این دو ADC دو برابر یعنی 2 Ms/s دستیابیم. کاربرد این سرعت نمونهبرداری برای سیگنالهایی است که فرکانس آنها بیش از 500KHz است. در این حالت با توجه به نرخ نایکویست، فرکانس نمونهبرداری که باید حداقل دو برابر فرکانس سیگنال باشد، بیش از 1 Ms/s میشود که با یک ADC امکانپذیر نیست.
در شکل بالا میبینیم که در صورت تنظیم واحدهای ADC در حالت Dual، این امکان وجود دارد که ADC با سرعت دو برابر روی یک کانال داشته باشیم.
ایجاد پروژه
بدین منظور باید در هنگام ایجاد پروژه بعد از تنظیم کلاک و دیباگ، کانال 0 مربوط به هر دو واحد ADC1 و ADC2 را فعال کنیم و سپس ADC1 را روی حالت Dual fast interleaved mode only تنظیم کنیم. بدین طریق ADC2 نیز در همین حالت قرار میگیرد. همچنین باید برای هر دو واحد ADC حالت Continuous Conversion mode را فعال کنیم و زمان نمونهبرداری را 1.5 سیکل قرار دهیم. در آخر DMA را برای واحد ADC1 فعال میکنیم:
سپس به سراغ تایمر1 میرویم و کانال 1 آن را روی حالت PWM تنظیم میکنیم. میخواهیم از این شکل موج برای آزمایش سرعت نمونهبرداری ADC استفاده کنیم. با تنظیمات زیر فرکانس شکل موج تولید شده 900KHz خواهد بود.
تایمر 2 را نیز برای شمارش تعداد پالس کلاکی که انجام نمونهگیری طول خواهد کشید، فعال میکنیم:
در آخر واحد USART3 را مشابه قبل برای فرستادن اطلاعات و چک کردن عملکرد پروژه فعال میکنیم. کلاک میکرو را روی 72MHz تنظیم کرده و کد پروژه را مانند قبل ایجاد میکنیم.
نوشتن کد پروژه
در ابتدای کد باید کتابخانه stdio را برای نمایش اطلاعات توسط printf اضافه کنیم و تابعهایی که از قبل تعریف کرده بودیم را به برنامه اضافه کنیم و این بار عمل ریتارگت را با USART3 انجام دهیم. مرحله مربوط به Project, Manage, Run-Time Environment را نیز باید در اینجا تکرار کنیم:
1 | #include "stdio.h" |
1 2 3 4 5 6 7 8 9 10 11 12 | /* Function for transmitting 8bit data via USART */ void write_uart(char data) { while(!LL_USART_IsActiveFlag_TXE(USART3)); LL_USART_TransmitData8(USART3, (uint8_t)data); } /* Retargeting stdout_putchar as to use USART_TX for data output */ int stdout_putchar (int ch) { write_uart(ch); return (ch); } |
بافر حافظه مورد نیاز را تعریف میکنیم:
1 2 3 | #define ARRAYSIZE (uint32_t) 100 uint32_t adc_read[ARRAYSIZE]; |
اکنون تنظیم و راهاندازی ADC ها و DMA را انجام میدهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ////ADC configuration LL_ADC_Enable(ADC1); LL_ADC_StartCalibration(ADC1); //Calibration start while(LL_ADC_IsCalibrationOnGoing(ADC1)) //Calibration Done? __NOP(); LL_ADC_REG_StartConversionSWStart(ADC1); LL_ADC_Enable(ADC2); LL_ADC_StartCalibration(ADC2); //Calibration start while(LL_ADC_IsCalibrationOnGoing(ADC2)) //Calibration Done? __NOP(); LL_ADC_REG_StartConversionSWStart(ADC2); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ////DMA Configuration LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1, LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA), (uint32_t)adc_read, //Address of the first element of the memory buffer LL_DMA_DIRECTION_PERIPH_TO_MEMORY); LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ARRAYSIZE); LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); |
در کد بالا تنها آدرس رجیستر ADC1 بهعنوان مبدأ اطلاعات تعیینشده است، زیرا با تنظیم ADC ها برای کار در حالت Dual مقدار تبدیلشده سپس باید تایمر 1 را که در حالت PWM تنظیم کرده بودیم، راهاندازی کنیم و همچنین تایمر 2 را برای شمارش فعال کنیم:
1 2 3 4 5 6 7 | ////Timers configuration LL_TIM_EnableCounter(TIM1); //Enable Timer1's counter LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1); //Enable channel1 of Timer1(PWM) LL_TIM_EnableAllOutputs(TIM1); //Enable Timer1's outputs LL_TIM_OC_SetCompareCH1(TIM1, (LL_TIM_GetAutoReload(TIM1) + 1) / 2); //Set Duty cycle LL_TIM_EnableCounter(TIM2); |
حالا در فایل stm32f1xx_it.c و در تابع وقفه DMA1 کد زیر را برای چاپ تعداد پالسهای زمان تبدیل مینویسیم (در این فایل نیز باید stdio.h را اضافه کنیم) :
1 2 3 4 5 6 7 8 9 10 11 | /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */ uint32_t count; if(LL_DMA_IsActiveFlag_TC1(DMA1) == 1) { LL_DMA_ClearFlag_TC1(DMA1); count = LL_TIM_GetCounter(TIM2); printf("count is %d\r\n", count); LL_TIM_SetCounter(TIM2, 0); } /* USER CODE END DMA1_Channel1_IRQn 0 */ |
نتیجه در ترمینال سریال قابل مشاهده است؛
اکنون برای مقایسه نحوه عملکرد دو واحد ADC بهصورت همزمان (با سرعت دو برابر) و یک ADC، به بخش دیباگ میرویم و بافر حافظه را به Watch1 اضافه میکنیم و سپس برنامه را اجرا میکنیم.
بعد از اجرا کردن برنامه میبینیم که بافر بهصورت زیر پرشده است:
میبینیم که نتیجه تبدیل ADC های 1 و 2 در 16 کمارزش و 16 بیت پرارزش هر خانه از بافر قرارگرفتهاند. برای حالتی که تنها یک ADC عمل نمونهبرداری و تبدیل را انجام دهد، میتوانیم از Cube MX یکی از واحدهای ADC را غیرفعال کنیم یا اینکه در کد برنامه، قسمت مربوط به فعالسازی ADC2 را کامنت کنیم. پس از اجرای کد در دیباگ داریم:
همانطور که میبینیم در این حالت نصف حالت قبلی نمونهبرداری انجامشده است.
برای رسم شکل موج اطلاعات دریافت شده، میتوانیم، کدهای printf قبلی را کامنت کنیم و کد زیر را برای حالت Dual به main.c اضافه کنیم:
1 2 3 4 5 6 | /* send array through usart_tx to serial plotter */ for(int i = 0; i < ARRAYSIZE; i++) { printf("%d\r\n", 0x0000FFFF & adc_read[i]); printf("%6.0f\r\n", (float)((0xFFFF0000 & adc_read[i]) / 65535)); } |
برای دیدن شکل موج میتوانیم از ابزار Serial Plotter در نرمافزار Arduino IDE استفاده کنیم؛
در بخش بعدی در مورد ارتباط I2C صحبت خواهیم کرد.