Показ дописів із міткою photoresistor. Показати всі дописи
Показ дописів із міткою photoresistor. Показати всі дописи

субота, 14 травня 2022 р.

MTB: PSoC4 CY8CKIT-045S прочитаємо значення ADC, порухаємо SERVO за допомоги PWM, розберемось з GPIO I/O interrupt

 Передмова

В попередніх статтях "Встановлення ModusToolBox та перший проект на CY8CKIT-149 PSoC® 4100S Plus Prototyping Kit" та "MTB: PSoC4 CY8CKIT-149 дослідимо аналогові блоки ADC та LPCOMP за допомоги PDL" розібрались як встановити MTB, навчились створювати проект для PSoC4 CY8CKIT-149. А також розібрались як налатувати і скористатись такими аналоговими блоками, як ADC і LPCOMP.

 В цьому дописі пропоную спробувати вже PSoC4 CY8CKIT-045S. Плата має ARDUINO сумісний роз'єм і щоб не збирати макет з купою дротів, скористаємось нагодою і спробуємо провести експерименти з мультифункціональною дошкою для ARDUINO сумісного форм-фактору.

Макет для дослідження
Зручно, без дротів, приєднати дошку з різноманітною периферією, до дошки з мікроконтролером і зосередитись на програмуванні. Але все ж таки, ще потрібно додати 2 сервомашинки. Для них є спеціальний роз'єм на дошці з шпильками D7, D8. Все інше, а це: фоторезистор A1, змінний резистор A0, кнопка D2 і світлодіод D12 або D13 є в наявності на платі. Ні паяти, ні припасовувати дроти, не потрібно.

Створюємо проект

Відкриваємо ModusToolBox, створюємо проект, обираємо свій мікроконтролер, в нашому випадку це CY8CKIT-045S:

Обираємо потрібний мікроконтролер
Далі обираємо шаблон проекту як "пустий проект":
Обираємо шаблон проекту

Та даємо назву проекту, наприклад "CY8CKIT-045S_ADC_PWM".
Подвійним кліком миші розгортаємо дерево проекту в провіднику проектів "Project Explorer" відкриваємо головний файл "main.c":
Відкриваємо файл main.c

В панелі швидкого доступу запускаємо "Device Configurator" і займемось налаштуванням потрібної периферії:

ADC

Для цього проекту нам знадобиться один ADC з двома 12-бітними каналами. Шпильки A0, A1 з яких будемо зчитувати аналогові сигнали будуть доступні для ADC1:
Увімкнення ADC
  1. Вмикаємо PASS 1 12-bit SAR ADC 0
  2. Даємо йому псевдонім (Alias), як "ADC_1", тоді зручно буде ним оперувати в коді
Перейдемо до налаштувань ADC, це виглядає таким чином:
Налаштування ADC
  1. Обираємо джерело опорної напруги як Vdda, це буде 3.3V
  2. Встановлюємо два канали (для змінного резистора і фоторезистора)
  3. Обираємо якийсь вільний дільник як джерело тактування
Трішки нижче будуть розділи для налаштування каналів, їх буде 2. Єдине що потрібно зробити це обрати шпильки з яких будемо читати аналоговий сигнал. Це шпильки A0 і A1, тут в BSP вони позначаються як CYBSP_A0 і CYBSP_A1. Обираємо саме їх:
Налаштування каналів ADC
Червоним позначив на що звернути увагу, щоб було так само для нашого, конкретного, прикладу.

PWM

В нашому прикладі-демонстрації, задіяно два сервопривода. Тому налаштуємо два таймери в режимі PWM. Нас цікавлять саме ті таймери в яких є доступ до шпильок D7 і D8, а це "TCPWM 16-bit Counter 4, 5":
Увімкнення двох TCPWM
  1. Активуємо лічильник 4 і 5
  2. Обираємо режим PWM
  3. Даємо їм псевдоніми (Alias) такі як "SERVO1", "SERVO2", надалі в коді буде зручно звертатись до них по псевдоніму
Перед тим як перейдемо до налаштувань PWM, спочатку налаштуємо дільник з якого будуть наші PWM тактуватись. Перейдемо до вкладки "Peripheral-Clock" і оберемо окремий "divider" для потреб PWM:
Налаштування джерела тактування для PWM
  1. Оберемо вільний дільник, в прикладі це "16 bit Devider 2", та дамо йому псевдонім (Alias), як PWM. 
  2. Дивимось яка частота на вхід дільника приходить, в нашому випадку це 48МГц
  3. Назначаємо значення для дільника, як 48, 
  4. На виході дільник буде 1МГц (48МГц/48 = 1МГц). Саме від цієї частоти будуть тактуватись TCPWM.
  5. Спочатку тут ще нічого не буде, тому що від якого дільника тактуватись PWM визначемо в наступному кроці.
Повертаємось до налаштувань PWM, для двох PWM будуть однакові налаштування, то ж покажу як налаштувати один канал PWM, а інший буде мати абсолютно такі ж значення:
Налаштування PWM каналу
  1. Режим таймера PWM
  2. В нашому випадку дільник 1, не рухаємо
  3. Період 19999. Тактуємось від джерела в 1МГц. А для серви потрібна частота 50Гц, а це 0.02 секунди. Тому 1000000*0.02=20000. Так як в лічильник рахує з 0, то 20000-1=19999.
  4. Серва працює від довжини періоду і має крайні значення періоду ~0.5 - 2.5 мілісекунди, а це будуть значення для "Compare" від 500-1=499 до 2500-1=2499. Початкове значення виставляємо не крайове, щоб точно знати що позиція сервомашинки не виходить за межі фізичного обмеження, тому обрав значення як - 999.
  5. От тут вже і обираємо "16 bit Divider 2 clk (PWM)", який ми попередньо налаштували на 1МГц.
  6. Та оберемо саме на які шпильки буде подаватись сигнал PWM, як на малюнках нижче:

GPIO

З вкладки "Peripheral" перейдемо до вкладки "Pins" і налаштуємо дві шпильки. D2 як вхід з перериванням, а D12 на вихід:
Вмикаємо GPIO
Цим шпилькам призначений вже псевдонім (Alias), такий як CYBSP_D12 який відповідає P5_1 шпильці і CYBSP_D2 який відповідає P5_5 шпильці. В коді ми будемо звертатись по псевдонімам до цих шпильок.
Налаштування для кнопки з перериваннями CYBSP_D2 виглядає так:
Налаштування кнопки з перериванням
  1. Режим вхід з підтяжкою до плюса "Resestive Pull-Up. Input buffer on"
  2. Початкове значення високий рівень "High(1)"
  3. Винекниння переривання по спадаючому фронту "Falling Edge"
Налаштування шпильки для світлодіода виглядатиме так:
Налаштування шпильки світлодіода
  1. Режим на вихід "Strong Drive. Input buffer off"
  2. Початкове значення "Low(0)". Світлодіод вимкнено.
З налаштуваннями периферії покінчено, можемо зберегти зміни за допомоги гарячих комбінацій - CTRL+S і перейти до створення коду-демонстрації

Демо-код

Для того щоб було зручно друкувати повідомлення в серіал-термінал за допомоги "printf" потрібно додати бібліотеку "cy_retarget_io.h". Запускаємо "Library manager" і додаємо:
Додаємо "cy_retarget_io"
Робимо всі кроки як на малюнку і перейдемо до кодування нашого демо-проекту. Відкриємо файл "main.c". Початково він виглядатиме так:
#include "cy_pdl.h"
#include "cybsp.h"

int main(void)
{
    cy_rslt_t result;

    /* Initialize the device and board peripherals */
    result = cybsp_init() ;
    if (result != CY_RSLT_SUCCESS)
    {
        CY_ASSERT(0);
    }

    /* Enable global interrupts */
    __enable_irq();

    for (;;)
    {
    }
}
А тепер можна додати демо-код, який я надам нижче. Розписувати кожен рядок не має сенсу, все доволі просто. А кому цікаво, або не зрозумілі якісь деталі то перегляньте відео-посібник в кінці цієї статті, де я відтворю всі кроки створення налаштування та кодування - детальніше.
То ж коли додамо демо-код, то наш файл "main.c" виглядатиме так:
#include "cy_pdl.h"
#include "cybsp.h"
#include "cy_retarget_io.h"
#include <stdbool.h>

#define SERVO_MAX	2399U
#define SERVO_MIN	599U

uint16_t resultADC1[2] = {0};
bool flagSwap = false;

cy_stc_sysint_t intKeyCfg = {
    .intrSrc = CYBSP_D2_IRQ,
    .intrPriority = 3UL
};

int map(int st1, int fn1, int st2, int fn2, int value)
{
    return (int)((float)(value - st1) * (fn2 - st2) / (float)(fn1 - st1) + st2);
}

void servo1_write(int angle)
{
    Cy_TCPWM_PWM_SetCompare0(SERVO1_HW, SERVO1_NUM, map(2047, 0, SERVO_MIN, SERVO_MAX, angle));
}

void servo2_write(int angle)
{
    Cy_TCPWM_PWM_SetCompare0(SERVO2_HW, SERVO2_NUM, map(2047, 0, SERVO_MIN, SERVO_MAX, angle));
}

void KeyISR(void)
{
    Cy_GPIO_Inv(CYBSP_D12_PORT, CYBSP_D12_PIN);
    flagSwap = !flagSwap;
    Cy_SysLib_Delay(50u);
    Cy_GPIO_ClearInterrupt(CYBSP_D2_PORT, CYBSP_D2_PIN);
}

int main(void)
{
    cy_rslt_t result;

    /* Initialize the device and board peripherals */
    result = cybsp_init() ;
    
    if (result != CY_RSLT_SUCCESS)
    {
        CY_ASSERT(0);
    }

    result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);

    if (result != CY_RSLT_SUCCESS)
    {
	CY_ASSERT(0);
    }

    Cy_SysInt_Init(&intKeyCfg, KeyISR);
    NVIC_ClearPendingIRQ(intKeyCfg.intrSrc);
    NVIC_EnableIRQ(intKeyCfg.intrSrc);

    /* Enable global interrupts */
    __enable_irq();

    printf("\x1b[2J\x1b[;H");
    printf("*------------------------------*\r\n");
    printf("*   Demo ADC and PWM started!  *\r\n");
    printf("*------------------------------*\r\n");

    result = Cy_SAR_Init(ADC_1_HW, &ADC_1_config);

    if (result != CY_RSLT_SUCCESS)
    {
	CY_ASSERT(0);
    }

    Cy_SAR_Enable(ADC_1_HW);

    result = Cy_TCPWM_PWM_Init(SERVO1_HW, SERVO1_NUM, &SERVO1_config);

    if (result != CY_RSLT_SUCCESS)
    {
	CY_ASSERT(0);
    }

    result = Cy_TCPWM_PWM_Init(SERVO2_HW, SERVO2_NUM, &SERVO2_config);

    if (result != CY_RSLT_SUCCESS)
    {
	CY_ASSERT(0);
    }

    Cy_TCPWM_PWM_Enable(SERVO1_HW, SERVO1_NUM);
    Cy_TCPWM_PWM_Enable(SERVO2_HW, SERVO2_NUM);
    Cy_TCPWM_TriggerStart(SERVO1_HW, SERVO1_MASK);
    Cy_TCPWM_TriggerStart(SERVO2_HW, SERVO2_MASK);

    for (;;)
    {
    	Cy_SAR_StartConvert(ADC_1_HW, CY_SAR_START_CONVERT_SINGLE_SHOT);
        Cy_SAR_IsEndConversion(ADC_1_HW, CY_SAR_WAIT_FOR_RESULT);
	resultADC1[0] = Cy_SAR_GetResult16(ADC_1_HW, 0);
	resultADC1[1] = Cy_SAR_GetResult16(ADC_1_HW, 1);

	if(flagSwap)
	{
	    servo1_write(resultADC1[1]);
	    servo2_write(resultADC1[0]);
	}
	else
	{
	    servo1_write(resultADC1[0]);
	    servo2_write(resultADC1[1]);
	}

	printf("ADC1 Result Channel 0 = %4d\r\n", resultADC1[0]);
	printf("ADC1 Result Channel 1 = %4d\r\n", resultADC1[1]);
	printf("\x1b[1F");
	printf("\x1b[1F");
	Cy_SysLib_Delay(20);
    }
}
Компілюємо, заливаємо, спостерігаємо. Резистором на A0 крутимо одну серву, а інша серва приймає положення в залежності від освітлення фоторезистора на A1. Кнопкою на D2 міняємо місцями серви.

Відео посібник