がれすたさんのDIY日記

電子回路、Python、組み込みシステム開発、自作エフェクターを語るblog

MIMXRT1050のUSB AudioでMIDIStreamingSubClassを使う1

現状USB MIDI機器として認識するようになったのでまとめておきます。
MCUXpressoSDK USB libのaudio_generator_lite_bmのusb_device_descriptor.cを修正する形で書いたのでそのまま置き換えればPCからはUSB MIDI機器として認識されると思います。

/*
 * Copyright (c) 2015 - 2016, Freescale Semiconductor, Inc.
 * Copyright 2016 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#include "usb_device_config.h"
#include "usb.h"
#include "usb_device.h"

#include "usb_device_audio.h"
#include "usb_audio_config.h"
#include "usb_device_descriptor.h"
#include "audio_generator.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*******************************************************************************
* Variables
******************************************************************************/
uint8_t g_UsbDeviceCurrentConfigure = 0U;
uint8_t g_UsbDeviceInterface[USB_AUDIO_GENERATOR_INTERFACE_COUNT];

/* Define device descriptor */
USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
uint8_t g_UsbDeviceDescriptor[] = {
    USB_DESCRIPTOR_LENGTH_DEVICE, /* Size of this descriptor in bytes */
    USB_DESCRIPTOR_TYPE_DEVICE,   /* DEVICE Descriptor Type */
    USB_SHORT_GET_LOW(USB_DEVICE_SPECIFIC_BCD_VERSION),
    USB_SHORT_GET_HIGH(USB_DEVICE_SPECIFIC_BCD_VERSION), /* USB Specification Release Number in
                                                            Binary-Coded Decimal (i.e., 2.10 is 210H). */
    USB_DEVICE_CLASS,                                    /* Class code (assigned by the USB-IF). */
    USB_DEVICE_SUBCLASS,                                 /* Subclass code (assigned by the USB-IF). */
    USB_DEVICE_PROTOCOL,                                 /* Protocol code (assigned by the USB-IF). */
    USB_CONTROL_MAX_PACKET_SIZE,                         /* Maximum packet size for endpoint zero
                                                            (only 8, 16, 32, or 64 are valid) */
    0xC9U, 0x1FU,                                        /* Vendor ID (assigned by the USB-IF) */
    0x97U, 0x00U,                                        /* Product ID (assigned by the manufacturer) */
    USB_SHORT_GET_LOW(USB_DEVICE_DEMO_BCD_VERSION),
    USB_SHORT_GET_HIGH(USB_DEVICE_DEMO_BCD_VERSION), /* Device release number in binary-coded decimal */
    0x01U,                                           /* Index of string descriptor describing manufacturer */
    0x02U,                                           /* Index of string descriptor describing product */
    0x00U,                                           /* Index of string descriptor describing the
                                                        device's serial number */
    USB_DEVICE_CONFIGURATION_COUNT,                  /* Number of possible configurations */
};

USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
uint8_t g_UsbDeviceConfigurationDescriptor[] = {
		 USB_DESCRIPTOR_LENGTH_CONFIGURE, /* Size of this descriptor in bytes */
		    USB_DESCRIPTOR_TYPE_CONFIGURE,   /* CONFIGURATION Descriptor Type */
			0x65U,0x00U,
		    USB_AUDIO_GENERATOR_INTERFACE_COUNT,                    /* Number of interfaces supported by this configuration */
		    USB_AUDIO_GENERATOR_CONFIGURE_INDEX,                    /* Value to use as an argument to the
		                                                               SetConfiguration() request to select this configuration */
		    0x00U, /* Index of string descriptor describing this configuration */
		    (USB_DESCRIPTOR_CONFIGURE_ATTRIBUTE_D7_MASK) |
		        (USB_DEVICE_CONFIG_SELF_POWER << USB_DESCRIPTOR_CONFIGURE_ATTRIBUTE_SELF_POWERED_SHIFT) |
		        (USB_DEVICE_CONFIG_REMOTE_WAKEUP << USB_DESCRIPTOR_CONFIGURE_ATTRIBUTE_REMOTE_WAKEUP_SHIFT),
		    /* Configuration characteristics
		       D7: Reserved (set to one)
		       D6: Self-powered
		       D5: Remote Wakeup
		       D4...0: Reserved (reset to zero)
		    */
		    USB_DEVICE_MAX_POWER, /* Maximum power consumption of the USB
		                           * device from the bus in this specific
		                           * configuration when the device is fully
		                           * operational. Expressed in 2 mA units
		                           *  (i.e., 50 = 100 mA).
		                           */

		    USB_DESCRIPTOR_LENGTH_INTERFACE,   /* Size of this descriptor in bytes */
		    USB_DESCRIPTOR_TYPE_INTERFACE,     /* INTERFACE Descriptor Type */
		    USB_AUDIO_CONTROL_INTERFACE_INDEX, /* Number of this interface. */
		    0x00U,                             /* Value used to select this alternate setting
		                                          for the interface identified in the prior field */
		    0x00U,                             /* Number of endpoints used by this
		                                          interface (excluding endpoint zero). */
		    USB_AUDIO_CLASS,                   /*The interface implements the Audio Interface class  */
		    USB_SUBCLASS_AUDIOCONTROL,         /*The interface implements the AUDIOCONTROL Subclass  */
		    0x00U,                             /*The interface doesn't use any class-specific protocols  */
		    0x00U,                             /* The device doesn't have a string descriptor describing this iInterface  */

		    /* Audio Class Specific type of INTERFACE Descriptor */
		    USB_AUDIO_CONTROL_INTERFACE_HEADER_LENGTH,   /* Size of the descriptor, in bytes  */
		    USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE,      /* CS_INTERFACE Descriptor Type   */
		    USB_DESCRIPTOR_SUBTYPE_AUDIO_CONTROL_HEADER, /* HEADER descriptor subtype   */
		    0x00U, 0x01U, /* Audio Device compliant to the USB Audio specification version 1.00  */
		    0x09U, 0x00U,  /* Total number of bytes returned for the class-specific AudioControl interface descriptor.
		                     Includes the combined length of this descriptor header and all Unit and Terminal
		                     descriptors. */
		    0x01U,        /* The number of AudioStreaming and MIDIStreaming interfaces in the Audio Interface Collection to
		                     which this AudioControl interface belongs   */
		    0x01U,        /* The number of AudioStreaming and MIDIStreaming interfaces in the Audio Interface baNumber */

			/*Standard MS Interface Descriptor*/
			0x09,//blength
			0x04,//bDescriptorType
			0x01,//bInterfaceNumber
			0x00,//bAlternateSetting
			0x02,//bNumEndpoints
			0x01,//bInterfaceClass
			0x03,//bInterfaceSubClass
			0x00,//bInterfaceProtocol
			0x00,//iInferface

			/*Class-specific MS Interface Descriptor*/
			0x07,      //bLength
			0x24,      //bDescriptorType
			0x01,	   //bDescriptorSubType
			0x00,0x01, //bcdMSC
			0x25,0x00, //wTotalLength

			/*MIDI IN Jack Descriptor Embedded*/
			0x06,//bLength
			0x24,//bDescriptorType
			0x02,//bDescriptorSubType
			0x01,//bJackType
			0x01,//bJackID
			0x00,//iJack

			/*MIDI IN Jack Descriptor external*/
			0x06,//bLength
			0x24,//bDescriptorType
			0x02,//bDescriptorSubType
			0x02,//bJackType
			0x02,//bJackID
			0x00,//iJack

			/*MIDI OUT Jack Descriptor Embedded*/
			0x09,//bLength
			0x24,//bDescriptorType
			0x03,//bDescriptorSubType
			0x01,//bJackType
			0x03,//bJackID
			0x01,//bNrInputPins
			0x02,//baSourceID(1)
			0x01,//baSourcePin(1)
			0x00,//iJack

			/*MIDI OUT Jack Descriptor External*/
			0x09,//bLength
			0x24,//bDescriptorType
			0x03,//bDescriptorSubType
			0x02,//bJackType
			0x04,//bJackID
			0x01,//bNrInputPins
			0x01,//baSourceID(1)
			0x01,//baSourcePin(1)
			0x00,//iJack

			/*MIDI Adapter Standard Bulk OUT Endpoint Descriptor*/
			0x09,//blength
			0x05,//bDescripterType    : ENDPOINT
			0x01,//bEndpointAddress   :
			0x02,//bmAttributes       : Bulk endpoint
			0x40,//wMaxPacketSize     : 0x0040 -> care endian(0x40,0x00)
			0x00,
			0x00,//bInterval          : No use(bulk endpoint)
			0x00,//bRefresh           : No use
			0x00,//bSynchAddress      : No use

			/*MIDI Adapter Class-specific Bulk OUT Endpoint Descriptor*/
			0x05,//blength
			0x25,//bDescripterType    : CS_ENDPOINT
			0x01,//bDescriptorSubType : MS_GENERAL
			0x01,//bNumEmbMIDIJack    : Number of Embedded MIDI jack
			0x01,//baAssocJack        : ID of the last Embedded Jack that is associated with this endpoint

			/* MIDI Adapter Standard Bulk IN Endpoint Descriptor*/
			0x09,//bLength
			0x05,//bDescripterType    : ENDPOINT
			0x81,//bEndpointAddress   :
			0x02,//bmAttributes       : Bulk endpoint
			0x40,//wMaxPacketSize     : 0x0040 -> care endian(0x40,0x00)
			0x00,//
			0x00,//bInterval          : No use(bulk endpoint)
			0x00,//bRefresh           : No use
			0x00,//bSynchAddress      : No use

			/*MIDI Adapter Class-specific Bulk IN Endpoint Descriptor*/
			0x05,//blength
			0x25,//bDescripterType    : CS_ENDPOINT
			0x01,//bDescriptorSubType : MS_GENERAL
			0x01,//bNumEmbMIDIJack    : Number of Embedded MIDI jack
			0x03,//baAssocJack        : ID of the last Embedded Jack that is associated with this endpoint

			/*Audio Class Specific Descriptor Types(bDescripterType)*/
			/*Reference from USB Device Class Definition for Audio Devices PDF page 99 Appendix A*/
			//0x20 -> CS_UNDIFINED
			//0x21 -> CS_DEVICE
			//0x22 -> CS_CONFIGURATION
			//0x23 -> CS_STRING
			//0x24 -> CS_INTERFACE
			//0x25 -> CS_ENDPOINT

			/*MS Class-Specific Interface Descriptor Subtypes(bDescriptorSubType)*/
			/*Reference from USB Device Class Definition for MIDI Devices PDF page 36 Appendix A*/
			//0x00 -> MS_DESCRIPTOR_UNDEFINED
			//0x01 -> MS_HEADER
			//0x02 -> MIDI_IN_JACK
			//0x03 -> MIDI_OUT_JACK
			//0x04 -> ELEMENT

			/*MS Class-Specific Endpoint Descriptor Subtypes(bDescriptorSubType)*/
			/*Reference from USB Device Class Definition for MIDI Devices PDF page 36 Appendix A*/
			//0x00 -> DESCRIPTOR_UNDEFINED
			//0x01 -> MS_GENERAL

			/*MS MIDI IN and OUT Jack types(bJackType)*/
			/*Reference from USB Device Class Definition for MIDI Devices PDF page 36 Appendix A*/
			//0x00 -> JACK_TYPE_UNDEFINED
			//0x01 -> EMBEDDED
			//0x02 -> EXTERNAL

			/*MIDI Adapter Standard Bulk Endpoint Address(bEndpointAddress)*/

			/*D7   : Direction*/
			//	0  : OUT endpoint
			//  1  : IN  endpoint
			/*D6..4 : Reserved , reset to zero*/
			/*D3..0 : The endpoint number ,determined by the designer.*/
			//for example 0x81(0b10000001) Direction is IN , endpoint number is 1 (IN EP 1)
};

/* Define string descriptor */
USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
uint8_t g_UsbDeviceString0[] = {
    2U + 2U, USB_DESCRIPTOR_TYPE_STRING, 0x09U, 0x04U,
};

USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
uint8_t g_UsbDeviceString1[] = {
    2U + 2U * 18U, USB_DESCRIPTOR_TYPE_STRING,
    'N',           0x00U,
    'X',           0x00U,
    'P',           0x00U,
    ' ',           0x00U,
    'S',           0x00U,
    'E',           0x00U,
    'M',           0x00U,
    'I',           0x00U,
    'C',           0x00U,
    'O',           0x00U,
    'N',           0x00U,
    'D',           0x00U,
    'U',           0x00U,
    'C',           0x00U,
    'T',           0x00U,
    'O',           0x00U,
    'R',           0x00U,
    'S',           0x00U,
};

USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
uint8_t g_UsbDeviceString2[] = {
    2U + 2U * 14U, USB_DESCRIPTOR_TYPE_STRING,
    'R',           0x00U,
    'T',           0x00U,
    ' ',           0x00U,
    ' ',           0x00U,
    '1',           0x00U,
    '0',           0x00U,
    '5',           0x00U,
    '2',           0x00U,
    ' ',           0x00U,
    ' ',           0x00U,
    'M',           0x00U,
    'I',           0x00U,
    'D',           0x00U,
    'I',           0x00U,
};

uint32_t g_UsbDeviceStringDescriptorLength[USB_DEVICE_STRING_COUNT] = {
    sizeof(g_UsbDeviceString0), sizeof(g_UsbDeviceString1), sizeof(g_UsbDeviceString2),
};

uint8_t *g_UsbDeviceStringDescriptorArray[USB_DEVICE_STRING_COUNT] = {
    g_UsbDeviceString0, g_UsbDeviceString1, g_UsbDeviceString2,
};

usb_language_t g_UsbDeviceLanguage[USB_DEVICE_LANGUAGE_COUNT] = {{
    g_UsbDeviceStringDescriptorArray, g_UsbDeviceStringDescriptorLength, (uint16_t)0x0409U,
}};

usb_language_list_t g_UsbDeviceLanguageList = {
    g_UsbDeviceString0, sizeof(g_UsbDeviceString0), g_UsbDeviceLanguage, USB_DEVICE_LANGUAGE_COUNT,
};

/*******************************************************************************
* Code
******************************************************************************/
/*!
 * @brief Get the descriptor.
 *
 * The function is used to get the descriptor, including the device descriptor, configuration descriptor, and string
 * descriptor, etc.
 *
 * @param handle              The device handle.
 * @param setup               The setup packet buffer address.
 * @param length              It is an OUT parameter, return the data length need to be sent to host.
 * @param buffer              It is an OUT parameter, return the data buffer address.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceGetDescriptor(usb_device_handle handle,
                                     usb_setup_struct_t *setup,
                                     uint32_t *length,
                                     uint8_t **buffer)
{
    uint8_t descriptorType = (uint8_t)((setup->wValue & 0xFF00U) >> 8U);
    uint8_t descriptorIndex = (uint8_t)((setup->wValue & 0x00FFU));
    usb_status_t ret = kStatus_USB_Success;
    if (USB_REQUEST_STANDARD_GET_DESCRIPTOR != setup->bRequest)
    {
        return kStatus_USB_InvalidRequest;
    }
    switch (descriptorType)
    {
        case USB_DESCRIPTOR_TYPE_STRING:
        {
            /* Get string descriptor */
            if (0U == descriptorIndex)
            {
                *buffer = (uint8_t *)g_UsbDeviceLanguageList.languageString;
                *length = g_UsbDeviceLanguageList.stringLength;
            }
            else
            {
                uint8_t languageId = 0U;
                uint8_t languageIndex = USB_DEVICE_STRING_COUNT;

                for (; languageId < USB_DEVICE_LANGUAGE_COUNT; languageId++)
                {
                    if (setup->wIndex == g_UsbDeviceLanguageList.languageList[languageId].languageId)
                    {
                        if (descriptorIndex < USB_DEVICE_STRING_COUNT)
                        {
                            languageIndex = descriptorIndex;
                        }
                        break;
                    }
                }

                if (USB_DEVICE_STRING_COUNT == languageIndex)
                {
                    return kStatus_USB_InvalidRequest;
                }
                *buffer = (uint8_t *)g_UsbDeviceLanguageList.languageList[languageId].string[languageIndex];
                *length = g_UsbDeviceLanguageList.languageList[languageId].length[languageIndex];
            }
        }
        break;
        case USB_DESCRIPTOR_TYPE_DEVICE:
        {
            /* Get device descriptor */
            *buffer = g_UsbDeviceDescriptor;
            *length = USB_DESCRIPTOR_LENGTH_DEVICE;
        }
        break;
        case USB_DESCRIPTOR_TYPE_CONFIGURE:
        {
            /* Get configuration descriptor */
            *buffer = g_UsbDeviceConfigurationDescriptor;
            *length = USB_DESCRIPTOR_LENGTH_CONFIGURATION_ALL;
        }
        break;
        default:
            ret = kStatus_USB_InvalidRequest;
            break;
    } /* End Switch */
    return ret;
}

/*!
 * @brief Set the device configuration.
 *
 * The function is used to set the device configuration.
 *
 * @param handle              The device handle.
 * @param configure           The configuration value.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceSetConfigure(usb_device_handle handle, uint8_t configure)
{
    if (!configure)
    {
        return kStatus_USB_Error;
    }
    g_UsbDeviceCurrentConfigure = configure;
    return USB_DeviceCallback(handle, kUSB_DeviceEventSetConfiguration, &configure);
}

/*!
 * @brief Get the device configuration.
 *
 * The function is used to get the device configuration.
 *
 * @param handle The device handle.
 * @param configure It is an OUT parameter, save the current configuration value.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceGetConfigure(usb_device_handle handle, uint8_t *configure)
{
    *configure = g_UsbDeviceCurrentConfigure;
    return kStatus_USB_Success;
}

/*!
 * @brief Set an interface alternate setting.
 *
 * The function is used to set an interface alternate setting.
 *
 * @param handle The device handle.
 * @param interface The interface index.
 * @param alternateSetting The new alternate setting value.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceSetInterface(usb_device_handle handle, uint8_t interface, uint8_t alternateSetting)
{
    g_UsbDeviceInterface[interface] = alternateSetting;
    return USB_DeviceCallback(handle, kUSB_DeviceEventSetInterface, &interface);
}

/*!
 * @brief Get an interface alternate setting.
 *
 * The function is used to get an interface alternate setting.
 *
 * @param handle The device handle.
 * @param interface The interface index.
 * @param alternateSetting It is an OUT parameter, save the new alternate setting value of the interface.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceGetInterface(usb_device_handle handle, uint8_t interface, uint8_t *alternateSetting)
{
    *alternateSetting = g_UsbDeviceInterface[interface];
    return kStatus_USB_Success;
}

/* Due to the difference of HS and FS descriptors, the device descriptors and configurations need to be updated to match
 * current speed.
 * As the default, the device descriptors and configurations are configured by using FS parameters for both EHCI and
 * KHCI.
 * When the EHCI is enabled, the application needs to call this function to update device by using current speed.
 * The updated information includes endpoint max packet size, endpoint interval, etc. */
usb_status_t USB_DeviceSetSpeed(uint8_t speed)
{
    usb_descriptor_union_t *descriptorHead;
    usb_descriptor_union_t *descriptorTail;

    descriptorHead = (usb_descriptor_union_t *)&g_UsbDeviceConfigurationDescriptor[0];
    descriptorTail =
        (usb_descriptor_union_t *)(&g_UsbDeviceConfigurationDescriptor[USB_DESCRIPTOR_LENGTH_CONFIGURATION_ALL - 1U]);

    while (descriptorHead < descriptorTail)
    {
        if (descriptorHead->common.bDescriptorType == USB_DESCRIPTOR_TYPE_ENDPOINT)
        {
            if (USB_SPEED_HIGH == speed)
            {
                if (USB_AUDIO_STREAM_ENDPOINT == (descriptorHead->endpoint.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK))
                {
                    descriptorHead->endpoint.bInterval = HS_ISO_IN_ENDP_INTERVAL;
                    USB_SHORT_TO_LITTLE_ENDIAN_ADDRESS(HS_ISO_IN_ENDP_PACKET_SIZE,
                                                       descriptorHead->endpoint.wMaxPacketSize);
                }
                else
                {
                    descriptorHead->endpoint.bInterval = HS_INTERRUPT_IN_INTERVAL;
                    USB_SHORT_TO_LITTLE_ENDIAN_ADDRESS(HS_INTERRUPT_IN_PACKET_SIZE,
                                                       descriptorHead->endpoint.wMaxPacketSize);
                }
            }
            else
            {
                if (USB_AUDIO_STREAM_ENDPOINT == (descriptorHead->endpoint.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK))
                {
                    descriptorHead->endpoint.bInterval = FS_ISO_IN_ENDP_INTERVAL;
                    USB_SHORT_TO_LITTLE_ENDIAN_ADDRESS(FS_ISO_IN_ENDP_PACKET_SIZE,
                                                       descriptorHead->endpoint.wMaxPacketSize);
                }
                else
                {
                    descriptorHead->endpoint.bInterval = FS_INTERRUPT_IN_INTERVAL;
                    USB_SHORT_TO_LITTLE_ENDIAN_ADDRESS(FS_INTERRUPT_IN_PACKET_SIZE,
                                                       descriptorHead->endpoint.wMaxPacketSize);
                }
            }
        }
        descriptorHead = (usb_descriptor_union_t *)((uint8_t *)descriptorHead + descriptorHead->common.bLength);
    }

    return kStatus_USB_Success;
}

MIMXRT10xxのUART DMAメモ

前回は割り込みについてやったので今回はDMAについてメモを書く
参考には公式サンプルのlpuart_edma_transferを使うのでそれを参考にしてください。

公式サンプルが動作コードなので使い方的なのは解説しません

DMA部分のメモ

ポーリング、割り込みとは違いDMAMUXEDMAAPIが新しく出てくるのでそれの説明からしていく。

    /*Initはクロックの供給をしている*/
    DMAMUX_Init(EXAMPLE_LPUART_DMAMUX_BASEADDR);

 /*ソースとチャンネルの設定*/
    DMAMUX_SetSource(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_TX_DMA_CHANNEL, LPUART_TX_DMA_REQUEST);
    DMAMUX_SetSource(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_RX_DMA_CHANNEL, LPUART_RX_DMA_REQUEST);

 /*チャンネルの有効*/
    DMAMUX_EnableChannel(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_TX_DMA_CHANNEL);
    DMAMUX_EnableChannel(EXAMPLE_LPUART_DMAMUX_BASEADDR, LPUART_RX_DMA_CHANNEL);


f:id:gsmcustomeffects:20191012214331p:plain
DMAMUX_SetSourceの設定だが引数がbase,channel,sourceである。
channelはDMAのチャンネルなので好きなチャンネルを設定すればいい
sourceはペリフェラル側なので決まっている値使うペリフェラルごとにMIMXRT1052.hで定義されている。
f:id:gsmcustomeffects:20191012214721p:plain

LPUARTの場合は送受信で2,3を使っている。
DMAMUX_EnableChannelのほうは0,1をチャンネルとして設定している。

    /*デフォルト設定の読み込み*/
    EDMA_GetDefaultConfig(&config);

    /*EDMAの初期化*/
    EDMA_Init(EXAMPLE_LPUART_DMA_BASEADDR, &config);

    /*ハンドル設定/
    EDMA_CreateHandle(&g_lpuartTxEdmaHandle, EXAMPLE_LPUART_DMA_BASEADDR, LPUART_TX_DMA_CHANNEL);
    EDMA_CreateHandle(&g_lpuartRxEdmaHandle, EXAMPLE_LPUART_DMA_BASEADDR, LPUART_RX_DMA_CHANNEL);

EDMAに関してはInitしてHandleの登録するだけ。

LPUART_TransferCreateHandleEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, LPUART_UserCallback, NULL, &g_lpuartTxEdmaHandle,
                                    &g_lpuartRxEdmaHandle);

最後にLPUART_TransferCreateHandleEDMAでUART用のハンドルを作成する。
コールバックやEDMAHandleを登録する。

あとは送受信APIを呼ぶだけ。

LPUART_ReceiveEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &receiveXfer);
LPUART_SendEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &sendXfer);

コールバックに関してはサンプルと同様にstatus比較で処理分岐するのがいいと思う

void LPUART_UserCallback(LPUART_Type *base, lpuart_edma_handle_t *handle, status_t status, void *userData)
{
    userData = userData;

    if (kStatus_LPUART_TxIdle == status)
    {
        txBufferFull = false;
        txOnGoing = false;
    }

    if (kStatus_LPUART_RxIdle == status)
    {
        rxBufferEmpty = false;
        rxOnGoing = false;
    }
}

まとめ

LPUARTでDMAの使い方をまとめた。
LPUARTにDMA専用APIが用意されているので比較的楽に記述できる点が良いと感じた。

MIMXRT10xxのUART割り込みメモ

UARTの割り込みについて少し迷ったのでメモ代わりに残しておく。
コードに関しては公式のサンプル内にあるlpuart_interrupt_rb_transferってやつを参考に解説しているので全容はそっちを見てください。

送受信APIの確認

まず割り込みには以下に示すの二つのAPIを使うことになる。

LPUART_TransferReceiveNonBlocking(LPUART_Type *base,
                              lpuart_handle_t *handle,
                              lpuart_transfer_t *xfer,
                              size_t *receivedBytes)
LPUART_TransferSendNonBlocking(LPUART_Type *base,
                              lpuart_handle_t *handle, 
                              lpuart_transfer_t *xfer)

handleはLPUART1のような感じでLPUARTモジュールのハンドルを渡す。
xferはデータ構造そのもので

typedef struct _lpuart_transfer
{
    uint8_t *data;   /*!< The buffer of data to be transfer.*/
    size_t dataSize; /*!< The byte count to be transfer. */
} lpuart_transfer_t;

のように定義している。

実際の使い方

実際の使い方はこんな感じ

LPUART_GetDefaultConfig(&config);//デフォルト設定を読み込む
config.baudRate_Bps = BOARD_DEBUG_UART_BAUDRATE;//このマクロだと115200
config.enableTx = true;//送信有効
config.enableRx = true;//受信有効
LPUART_Init(DEMO_LPUART, &config, DEMO_LPUART_CLK_FREQ);//初期化はポーリングと同じ


/*
 *ハンドルの生成
 *この時点でコールバック関数も登録する。
 *この例ではUserdataがNULLになっているがコールバックに何かデータ渡したいならここにポインタを入れる
 */
LPUART_TransferCreateHandle(DEMO_LPUART, &g_lpuartHandle, LPUART_UserCallback, NULL);


/*
 *リングバッファの有効
 *これの有無でLPUART_TransferReceiveNonBlocking内部での処理が変わる(後述)
 */
LPUART_TransferStartRingBuffer(DEMO_LPUART, &g_lpuartHandle, g_rxRingBuffer, RX_RING_BUFFER_SIZE);

/*
 *xfer構造体にバッファの登録をする。
 *データ構造はこの構造体が決めるのでデータプロトコルが複数ある場合は複数用意しておくと可視性が上がる
*/
xfer.data = g_tipString;//バッファは各自用意する
xfer.dataSize = sizeof(g_tipString) - 1;//サイズも各自編集

/*
 * ノンブロッキング送信API
 * 送信完了後コールバックに飛ぶ
 * あとはxfer構造体のバッファーにデータをセットしてこいつをコールすればいい
 *
*/
LPUART_TransferSendNonBlocking(DEMO_LPUART, &g_lpuartHandle, &xfer);

/*
 * ノンブロッキング受信API
 * 受信完了後コールバックに飛ぶ
*/
LPUART_TransferReceiveNonBlocking(DEMO_LPUART, &g_lpuartHandle, &receiveXfer, &receivedBytes);

リングバッファの話についてだがLPUART_TransferReceiveNonBlockingの中にコメント解説がある。

/* How to get data:

       1. If RX ring buffer is not enabled, then save xfer->data and xfer->dataSize
          to lpuart handle, enable interrupt to store received data to xfer->data. When
          all data received, trigger callback.
       2. If RX ring buffer is enabled and not empty, get data from ring buffer first.
          If there are enough data in ring buffer, copy them to xfer->data and return.
          If there are not enough data in ring buffer, copy all of them to xfer->data,
          save the xfer->data remained empty space to lpuart handle, receive data
          to this empty space and trigger callback when finished. 
*/

用はLPUART_TransferStartRingBufferをコールして有効にしていると受信レジスタとxfer->dataの間にリングバッファが挟まるという感じです。
直訳するとリングバッファーに十分なデータがある場合は、それらをxfer-> dataにコピーして戻る。
リングバッファーに十分なデータがない場合は、それらすべてをxfer-> dataにコピーし、xfer-> dataの空きスペースをlpuartハンドルに保存。そのあとこの空きスペースにデータを受信し、終了時にコールバックをトリガーします。

LPUART_TransferReceiveNonBlockingで設定したデータ数が受信されるまでリングバッファとxfer-> data内でやりくりしてくれる。
あとは公式サンプルのようにrxOnGoing変数を監視してLPUART_TransferReceiveNonBlockingをコールしてもいいし

LPUART_TransferReceiveNonBlockingの返り値がステータスなのでそいつを頼りにしてBusyなら再コールするなりで対応すればいいと思います。


タイマーで定期的に受信したいなら

if(g_lpuartHandle.rxState == kStatus_LPUART_RxIdle){........}

みたいな書き方でもいいと思う

PySide2でQtQuick(qml)使うメモ3

今回はPySide2とPySerialを組み合わせて使うメモ

やることとしてはQMLで作成したGUI側でイベントを発生させてシリアルで何か送信してマイコンを制御するという感じ
つくったのはこんな感じのやつ

f:id:gsmcustomeffects:20191005005003p:plain

COMの選択をしてオープンをすると5~7のオブジェクトが表示される仕組み クローズすると消えるようになってる

f:id:gsmcustomeffects:20191005010331p:plain

コード全文は下に貼るのでちょい特殊な部分だけ解説していく流れで行きます。

尚基礎に関しては一番下に過去の記事貼っていますのでそちらを参考に環境構築などして下さい

Python側実装説明

class SerialComport(QtCore.QObject):
    def __init__(self, parent=None):
        super(SerialComport, self).__init__(parent)
        self.flag = 0
        self.instance = []

    @QtCore.Slot(result = 'QVariant')
    def comlist(self):
        self.ports = list_ports.comports()
        self.devices = [info.device for info in self.ports]
        self.instance.clear()                                   #clear list
        for i in range(len(self.devices)):
            self.instance.append(serial.Serial(port=self.devices[i],baudrate=9600))#instance packing
            self.instance[i].close()
        return self.devices

self.ports = list_ports.comports()でCOMの取得をしている。
self.instance.appendってのでインスタンスを配列みたいに管理できる。
これのおかげで self.instance[1].closeみたいな感じでアクセス可能になるわけ.
めんどくさかったのでボーレートは9600固定(GUI側でボーレート指定すれば実現は可能)
return self.devicesでQt側にlistで返している('QVariant'なので色々返せる)

Pyserialのほうは殆どAPI使ってるだけなので特にないけど送信はこんな感じで書いてる

    @QtCore.Slot(str,int,int,int)
    def slider_changed(self, arg1,r,g,b):
        for i in range(len(self.devices)):
            if (self.instance[i].port == arg1):
                self.instance[i].write(b.to_bytes(1, 'big'))
                self.instance[i].write(g.to_bytes(1, 'big'))
                self.instance[i].write(r.to_bytes(1, 'big'))

Qt(qml)側の説明

function comupdate(){
        comboBox.model.clear()
        var device = SerialComport.comlist();
        for(var key in device){
            comboBox.model.append({text:device[key]});
        }
        comboBox.currentIndex = 1;
    }

起動時の処理を毎度書くのがめんどかったのでfunctionにした。
あとはcomboBoxのmodelプロパティにアクセスしCOMリストを更新している感じ

function part_visible(){
        radioDelegate.visible = true
        radioDelegate1.visible = true
        radioDelegate2.visible = true
        radioDelegate3.visible = true
        slider.visible = true
        slider1.visible = true
        slider2.visible = true
        rectangle.visible = true
    }

    function part_hide(){
        radioDelegate.visible = false
        radioDelegate1.visible = false
        radioDelegate2.visible = false
        radioDelegate3.visible = false
        slider.visible = false
        slider1.visible = false
        slider2.visible = false
        rectangle.visible = false
    }

これはプロパティ表示/非表示の命令であり、各種コンポーネントのvisibleプロパティにtrue/falseしているだけ。

まとめ

Pyserial + PySide2で簡単なCOMアプリケーションを構成することができた。
結構手抜き実装なのでちゃんとやるならtryとか入れたほうがいいと思う。あとはisOpenのケアをもっとまじめにするとかね。

エンディアンとかあんまし気にしてないので間違ってるかも。なのでそこはPyserialのドキュメントちゃんと読んでください。
最後に動作例の動画でも貼っときます


マイコン側参考コード

ハードはKinetis FRDM-K22FでソフトはMbedを利用して書いてます。

/* mbed Microcontroller Library
 * Copyright (c) 2018 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 */

#include "mbed.h"
#include "stats_report.h"

PwmOut led1(LED1);//r
PwmOut led2(LED2);//g
PwmOut led3(LED3);//b


#define SLEEP_TIME                  500 // (msec)
#define PRINT_AFTER_N_LOOPS         20
Serial pc(USBTX, USBRX);
// main() runs in its own thread in the OS
int main()
{
    SystemReport sys_state( SLEEP_TIME * PRINT_AFTER_N_LOOPS /* Loop delay time in ms */);

    int count = 0;
    while (true) {
        char b = pc.getc();
        char g = pc.getc();
        char r = pc.getc();
            led1 = 1.0-1.0/255.0f*(float)r;//R
            led2 = 1.0-1.0/255.0f*(float)g;//G
            led3 = 1.0-1.0/255.0f*(float)b; //B   
        
        
    }
}

参考用コード全文(Python側)

import sys
import os
from PySide2 import QtCore, QtWidgets, QtQml
from serial.tools import list_ports
import serial

class SerialComport(QtCore.QObject):
    def __init__(self, parent=None):
        super(SerialComport, self).__init__(parent)
        self.flag = 0
        self.instance = []

    @QtCore.Slot(result = 'QVariant')
    def comlist(self):
        self.ports = list_ports.comports()
        self.devices = [info.device for info in self.ports]
        self.instance.clear()                                   #clear list
        for i in range(len(self.devices)):
            self.instance.append(serial.Serial(port=self.devices[i],baudrate=9600))#instance packing
            self.instance[i].close()
        return self.devices

    @QtCore.Slot(str)
    def comopen(self,arg1):
        for i in range(len(self.devices)):
            if(self.instance[i].port==arg1):
                self.instance[i].open()                         #COM open

    @QtCore.Slot(str)
    def comclose(self, arg1):
        for i in range(len(self.devices)):
            if (self.instance[i].port == arg1):
                self.instance[i].close()                        #COM close

    @QtCore.Slot(str,result='int')
    def comlistchanged(self,arg1):
        for i in range(len(self.devices)):
            if(self.instance[i].port==arg1):                    #checking which COM port is selected.
                if(self.instance[i].isOpen() == True):          #open or close check
                    self.flag = 1                               #if checked COM open, set flag.
                    print(self.instance[i].port + ":open")
                else:
                    self.flag = 0                               #if checked COM close, clear flag.
                    print(self.instance[i].port + ":close")
            else:
                if(self.instance[i].isOpen() == True):
                    print(self.instance[i].port + ":open")
                else:
                    print(self.instance[i].port + ":close")
        return self.flag

    @QtCore.Slot()
    def debug_alert(self):
        print("call!")                                         #QtQuick test function

    @QtCore.Slot(str)
    def ledred(self,arg1):
        for i in range(len(self.devices)):
            if (self.instance[i].port == arg1):
                packet = []
                packet.append(0x00)#b
                packet.append(0x00)#g
                packet.append(0xff)#r
                self.instance[i].write(packet)

    @QtCore.Slot(str)
    def ledblue(self, arg1):
        for i in range(len(self.devices)):
            if (self.instance[i].port == arg1):
                packet = []
                packet.append(0xff)#b
                packet.append(0x00)#g
                packet.append(0x00)#r
                print(packet)
                self.instance[i].write(packet)

    @QtCore.Slot(str)
    def ledgreen(self, arg1):
        for i in range(len(self.devices)):
            if (self.instance[i].port == arg1):
                packet = []
                packet.append(0x00)
                packet.append(0xff)
                packet.append(0x00)
                self.instance[i].write(packet)

    @QtCore.Slot(str)
    def ledorange(self, arg1):
        for i in range(len(self.devices)):
            if (self.instance[i].port == arg1):
                packet = []
                packet.append(0x00)
                packet.append(0x45)
                packet.append(0xff)
                self.instance[i].write(packet)

    @QtCore.Slot(str,int,int,int)
    def slider_changed(self, arg1,r,g,b):
        for i in range(len(self.devices)):
            if (self.instance[i].port == arg1):
                self.instance[i].write(b.to_bytes(1, 'big'))
                self.instance[i].write(g.to_bytes(1, 'big'))
                self.instance[i].write(r.to_bytes(1, 'big'))





if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"              #set Material theme(QtQuick)
    app = QtWidgets.QApplication(sys.argv)
    myconnect = SerialComport()                                     #Create instance


    engine = QtQml.QQmlApplicationEngine()                          #GUI
    ctx = engine.rootContext()
    ctx.setContextProperty("SerialComport", myconnect)              #Connect GUi to SerialComport Class
    engine.load('mypyside2.qml')                                    #qml file load.
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

コード全文(qml側)

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3


Window {

    function init(){
        comupdate();
    }

    function comupdate(){
        comboBox.model.clear()
        var device = SerialComport.comlist();
        for(var key in device){
            comboBox.model.append({text:device[key]});
        }
        comboBox.currentIndex = 1;
    }

    function part_visible(){
        radioDelegate.visible = true
        radioDelegate1.visible = true
        radioDelegate2.visible = true
        radioDelegate3.visible = true
        slider.visible = true
        slider1.visible = true
        slider2.visible = true
        rectangle.visible = true
    }

    function part_hide(){
        radioDelegate.visible = false
        radioDelegate1.visible = false
        radioDelegate2.visible = false
        radioDelegate3.visible = false
        slider.visible = false
        slider1.visible = false
        slider2.visible = false
        rectangle.visible = false
    }

    objectName: "a"
    visible: true
    width: 640
    height: 480
    color: "#e2dde2"
    title: qsTr("Controller")
    Component.onCompleted:{
        init();
    }
    Button {
        id: button
        x: 255
        y: 28
        text: qsTr("COM ")
        font.pointSize: 13
        onClicked:{
            comupdate();
        }
    }

    ComboBox {
        id: comboBox
        x: 25
        y: 28
        width: 198
        height: 40
        visible: true
        editable: false
        model: ListModel {
            id: model
        }
        onCurrentIndexChanged:{
            var flag = SerialComport.comlistchanged(comboBox.model.get(comboBox.currentIndex).text);
            if(flag == 1){
                part_visible();
            }
            else{
                part_hide();
            }

        }
    }

    Button {
        id: button1
        x: 369
        y: 28
        text: qsTr("OPEN")
        font.pointSize: 13
        onClicked:{
            SerialComport.comopen(comboBox.model.get(comboBox.currentIndex).text);
            part_visible();
        }
    }

    Button {
        id: button2
        x: 475
        y: 28
        text: qsTr("CLOSE")
        font.pointSize: 13
        onClicked:{
            SerialComport.comclose(comboBox.model.get(comboBox.currentIndex).text);
            part_hide();
        }
    }

    RadioDelegate {
        id: radioDelegate
        visible:true
        x: 81
        y: 106
        text: qsTr("Red")
        onClicked:{
            SerialComport.ledred(comboBox.model.get(comboBox.currentIndex).text)
        }
    }

    RadioDelegate {
        id: radioDelegate1
        visible:true
        x: 187
        y: 106
        text: qsTr("Blue")
        onClicked:{
            SerialComport.ledblue(comboBox.model.get(comboBox.currentIndex).text)
        }
    }

    RadioDelegate {
        id: radioDelegate2
        visible:true
        x: 293
        y: 106
        text: qsTr("Green")
        onClicked:{
            SerialComport.ledgreen(comboBox.model.get(comboBox.currentIndex).text)
        }
    }

    RadioDelegate {
        id: radioDelegate3
        x: 420
        y: 106
        text: qsTr("Orange")
        visible: true
        onClicked:{
            SerialComport.ledorange(comboBox.model.get(comboBox.currentIndex).text)
        }
    }

    Slider {
        id: slider
        visible:true
        x: 81
        y: 215
        width: 439
        height: 40
        stepSize: 1
        wheelEnabled: true
        to: 255
        font.family: "Courier"
        font.capitalization: Font.AllLowercase
        value: 50
        onValueChanged:{
            SerialComport.slider_changed(comboBox.model.get(comboBox.currentIndex).text,slider.value,slider1.value,slider2.value)
            rectangle.color = Qt.rgba(slider.value/slider.to,slider1.value/slider.to,slider2.value/slider.to,1)
        }
    }

    Slider {
        id: slider1
        visible:true
        x: 81
        y: 261
        width: 439
        height: 40
        stepSize: 1
        wheelEnabled: true
        to: 255
        value: 50
        onValueChanged:{
            SerialComport.slider_changed(comboBox.model.get(comboBox.currentIndex).text,slider.value,slider1.value,slider2.value)
            rectangle.color = Qt.rgba(slider.value/slider.to,slider1.value/slider.to,slider2.value/slider.to,1)
        }
    }

    Slider {
        id: slider2
        visible:true
        x: 81
        y: 307
        width: 439
        height: 40
        stepSize: 1
        wheelEnabled: true
        to: 255
        value: 50
        onValueChanged:{
            SerialComport.slider_changed(comboBox.model.get(comboBox.currentIndex).text,slider.value,slider1.value,slider2.value)
            rectangle.color = Qt.rgba(slider.value/slider.to,slider1.value/slider.to,slider2.value/slider.to,1)
        }
    }

    Rectangle {
        id: rectangle
        visible:true
        x: 541
        y: 261
        width: 56
        height: 46
        color:Qt.rgba(0,0,0,1)
    }
}

PySide2でQtQuick(qml)使うメモ2

前回に引き続き今回もPySide2+Qtquick(qml)のメモ

GUIだと結構定番の電卓っぽい奴の実装

f:id:gsmcustomeffects:20190818201337g:plain


Button押下でイベント発生させてTextInputからデータもらってTextInputに返すサンプルだと思ってくれればいい

Pythonコード

import sys
import os
from PySide2 import QtCore, QtWidgets, QtQml

class Connect(QtCore.QObject):
    def __init__(self, parent=None):
        super(Connect, self).__init__(parent)

    @QtCore.Slot(int,int,result = float)
    def sum(self,arg1,arg2):
        return arg1 + arg2

    @QtCore.Slot(int, int, result=float)
    def sub(self, arg1, arg2):
        return arg1 - arg2

    @QtCore.Slot(int, int, result=float)
    def mul(self, arg1, arg2):
        return arg1 * arg2

    @QtCore.Slot(int, int, result=float)
    def div(self, arg1, arg2):
        return arg1 / arg2

if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QtWidgets.QApplication(sys.argv)
    myconnect = Connect()
    engine = QtQml.QQmlApplicationEngine()
    ctx = engine.rootContext()
    ctx.setContextProperty("Connect", myconnect)
    engine.load('mypyside2.qml')
    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

qmlコード

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3
import QtQuick.Controls.Material 2.0

Window {
    objectName: "a"
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    TextInput {
        id: textInput1
        x: 104
        y: 108
        width: 80
        height: 37
        text: qsTr("12")
        font.pixelSize: 20
    }

    TextInput {

        id: textInput2
        x: 179
        y: 108
        width: 80
        height: 37
        text: qsTr("2")
        font.pixelSize: 20
    }

    TextInput {

        id: textInput3
        x: 270
        y: 108
        width: 80
        height: 37
        text: qsTr("1")
        font.pixelSize: 20
    }

    Row {
        id: row
        x: 104
        y: 186
        width: 427
        height: 119
        spacing: 9

        Button {
            objectName:"button"
            id: button1
            width: 100
            height: 50
            text: qsTr("add")
            font.pointSize: 14
            onClicked:function(){
                textInput3.text = Connect.sum(textInput1.text,textInput2.text)
            }
        }

        Button {
            id: button2
            width: 100
            height: 50
            text: qsTr("sub")
            font.pointSize: 13
            objectName: "button"
            onClicked:function(){
                textInput3.text = Connect.sub(textInput1.text,textInput2.text)
            }
        }

        Button {
            id: button3
            width: 100
            height: 50
            text: qsTr("mul")
            font.pointSize: 12
            objectName: "button"
            onClicked:function(){
                textInput3.text = Connect.mul(textInput1.text,textInput2.text)
            }
        }

        Button {
            id: button4
            width: 100
            height: 50
            text: qsTr("div")
            font.pointSize: 12
            objectName: "button"
            onClicked:function(){
                textInput3.text = Connect.div(textInput1.text,textInput2.text)
            }
        }
    }
}

解説的なやつ

python

@QtCore.Slot(int,int,result = float)
    def sum(self,arg1,arg2):
        return arg1 + arg2

addの例で解説する。
@QtCore.Slot(int,int,result = float)で引数の型と返り値の型をqmlに対して公開する。
あとはdefで関数を実装するだけ前回との違いはSlotでreturnしているとこ。
ここで注意が必要なのが書かなくてもエラーは出ないがresult = floatは必須ということ
書かないと何も値が返ってこない

qml側

TextInput {

        id: textInput3
        x: 270
        y: 108
        width: 80
        height: 37
        text: qsTr("1")
        font.pixelSize: 20
    }

Button {
            objectName:"button"
            id: button1
            width: 100
            height: 50
            text: qsTr("add")
            font.pointSize: 14
            onClicked:function(){
                textInput3.text = Connect.sum(textInput1.text,textInput2.text)
            }
        }

こちらもaddの例で説明する。

まずqmlオブジェクトに対してはtextInput3.textのようにid.propertyでアクセスできる。(qmlではオブジェクト以下のパラメータ類のことをPropertyと呼ぶ

次にonclicked部分で押下時の動作を定義している。
textInput3.textにpython側のsumの返り値を代入するということをしているだけだ。

あくまでqmlオブジェクトへの代入はqml側でやるというとこがQtの方針
PythonC++側からも子オブジェクトにアクセスできるがQt公式は推奨していない(参照関係がごちゃごちゃになるため

この関係を守るとUI設計者と内部実装側が完全分業可能なので合理的ではある。

PySide2でQtQuick(qml)使うメモ1

自分用のメモです。

自分の動作環境は

  • PyCharm 無料版
  • Python 3.7.4 or 3.6(仮想で両方で試した)
  • Pycharm内臓のVenvでパッケージ管理

導入

PythonでQtを扱うにはPyQtとPysideの2つがある。

verごとに書くとこんな感じ

  • Qt4 : PyQt4,PyiSde
  • Qt5 : PyQt5,PySide2

という感じになる。
現行で使うならQt5系を利用するほうがいいだろうということでPyQt5,PySide2を選ぶことになる。
選定にあたりいろいろググった結果PySide2ってのがQt公式がサポートするQt5バインディング(Qt for Pythonと呼ばれてるのがこれにあたる)らしいのでこれを使うことにする。

ui -> pyをする使い方

QtDesignerというソフトを使ってグラフィカルにUIをデザインしてそのファイル(.ui)をpyside2-uic.exeを使って.py拡張子ファイルに変換してインポートする手法のこと。
この方法が一番情報も多くて使いやすいと思う。

いろんな人が情報を公開しているのでその辺を読むといいだろう
note.mu

qiita.com

UIファイルの更新のたびにPyCharm側でpyside2-uic.exeを自動で呼び出すこともできる。
qiita.com

QtQuickについて

これについては公式の説明が分かりやすい
Qt Quick 入門 第1回: Qt Quick とは - Qt Japanese Blog

ようはuiファイルではなくqmlというjson風の表記を使ってUIをデザインしていく手法であり比較的楽にかっこいいUIを作成できる。

f:id:gsmcustomeffects:20190818004444p:plain

画像は私が作成したUIであるが5分ぐらいでこのぐらいのGUIを作成できる。

PySide2導入とテスト

早速本題に入る。
まずはPyCharmで新規プロジェクトをつくる。
f:id:gsmcustomeffects:20190818010410p:plain

上部メニューのFile/settingでこの画面が開くのでproject interpreterの画面まで持ってくる。
f:id:gsmcustomeffects:20190818010741p:plain

+ボタンをクリックして必要なモジュールをインストールしてくる。
ここではPySide2を検索して持ってくる。
f:id:gsmcustomeffects:20190818011621p:plain

インストールができたら.pyファイルを作成して以下のリンクのコードを実行してみる。
https://doc.qt.io/qtforpython/tutorials/basictutorial/dialog.html

このような画面が出ていればPySide2の導入はOK
f:id:gsmcustomeffects:20190818012746p:plain

PySide2でQtQuick(qml)ファイルを扱う

次にqmlファイルを作るためにQt Creatorをダウンロードしてくる。
Download Qt: Choose commercial or open source

アカウントとか聞かれますけどスキップで何とかなります。
適当に自分の欲しいものをインストールしてください

CreatorとQt5.x系があればOKです。

Qt Creatorをインストール出来たら開いて新規プロジェクトでUI Prototypeを選ぶ
f:id:gsmcustomeffects:20190818021459p:plain

キットに関しては適当に選ぶといいです。
するとこのような画面になるのでデザインをクリックします。
f:id:gsmcustomeffects:20190818021603p:plain

UIデザイン画面が開くので次にインポートタブを開きます。
f:id:gsmcustomeffects:20190818022028p:plain

QtQuick.ControlsとQtQuick.Controls.Materialsを追加
f:id:gsmcustomeffects:20190818022236p:plain

そうするとエレメントタブにボタン類が入ってくる
f:id:gsmcustomeffects:20190818022427p:plain

ボタンをドラッグしてUIに追加する。
f:id:gsmcustomeffects:20190818022542p:plain

次にできたqmlをPycharmプロジェクトに入れてあげる
f:id:gsmcustomeffects:20190818022837p:plain

そしたらこのコードを実行

import sys
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QUrl

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    engine.load(QUrl("mypyside2.qml"))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

先ほど作ったUIが表示できていればOK
f:id:gsmcustomeffects:20190818023149p:plain

ボタンへのイベント追加

次に配置したウィジェットPythonコードをつないでいく。

python code

import sys
import os
from PySide2 import QtCore, QtWidgets, QtQml

class Connect(QtCore.QObject):
    def __init__(self, parent=None):
        super(Connect, self).__init__(parent)

    @QtCore.Slot()
    def button_clicked(self):
        print("button clicked")



if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QtWidgets.QApplication(sys.argv)
    myconnect = Connect()
    engine = QtQml.QQmlApplicationEngine()
    ctx = engine.rootContext()
    ctx.setContextProperty("Connect", myconnect)
    engine.load('mypyside2.qml')
    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

qml code

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3
import QtQuick.Controls.Material 2.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Button {
        id: button
        x: 270
        y: 220
        text: qsTr("Button")
        onClicked:Connect.button_clicked()//python側のクラスとつなぐ
    }
}


実行してボタンをクリックするとprint文が実行されるはず。
f:id:gsmcustomeffects:20190818025353p:plain

説明(Python側)

まずQtCore.QObjectを継承したクラス(Connect)を作成する。
@QtCore.Slotでqml側にメソッドの存在を通知する。これによりqml側でdef以下を呼べるようになる。
@表記はPythont的に言うとデコレータってやつらしい。

os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"

Materialテーマを有効にする記述で特に必要はない
有効にしていると以下のリンクのようなことができる。
https://doc.qt.io/qt-5/qtquickcontrols2-material.html

ctx = engine.rootContext()
ctx.setContextProperty("Connect", myconnect)

engine内部の子ウィジェットを検索可能にする記述(あってるかわかんないけど
QMLにアクセスしたりQMLからアクセスしたりする場合はこの二行が必須っぽい

説明(qml側)

説明するといってもonClickedぐらい

クリックされた時の動作を記述するメンバー
ウィジェットごとにイベントが用意されてるSliderならonValueChangedみたいな感じで書ける。
Ctrl+Spaceで補完できるので何があるかは分かると思う。

メンバーにカーソルを合わせてF1を押すとウィジェットの説明を表示できる
f:id:gsmcustomeffects:20190818031331p:plain


参考文献

rootcontextの記述はC++も同様なので参考になった。
その他qmlとの相互イベント通知の参考になる。
QMLとC++のバインディング - Qiita

今回の記事はこの問題の劣化版みたいなものなのでこっちのスレッド読むともっといいのがつくれると思う
Connect python signal to QML ui slot with PySide2 - Stack Overflow

公式のサンプルとチュートリアル
QMLの例が2個しかないけど書き方の参考にはなる。
https://doc.qt.io/qtforpython/tutorials/index.html