Используем UART в Quectel OpenCPU

В этой статье рассмотрю работу с последовательными портами в OpenCPU на примере модуля M66 от Quectel и версии SDK 2.2. Данный материал можно рассматривать как логическое продолжение моей статьи по Hardware Design по модулям Quectel, но только здесь будет описание API для работы с последовательными портами также.

В модуле имеется 3 аппаратных UART порта с конфигурацией по умолчанию 115200 baudrate 8N1 и буфером данных в 2 кБ: MAIN UART, DEBUG UART и AUX UART, которые также нумеруются как UART1, UART2 и UART3 соответственно. UART1 9-пиновый последовательный порт с сигналами RTS/CTS, UART2 и UART3 — трехпроводные. Также UART2 является по умолчанию портом отладки (об этом чуть позже).

MAIN UART используется для получения AT команд, а значит к нему следует подключать внешний микроконтроллер, если он используется в вашем приложении, но следует знать, что логический уровень UART интерфейса равен 2.8 В, т.е. необходимо использовать понижение уровня на RXD выводе, я обычно ставлю простой делитель напряжения (если у вас микроконтроллер с 5 В логикой, то соответственно вам нужна другая схема преобразования на транзисторах или выходной буфер с открытым стоком типа NC7WZ07 или TXS0102PWR):

Рисунок 1. Делитель напряжения для преобразования уровня 3.3 В в 2.8 В логического уровня UART

Тут стоит еще отметить, что OpenCPU предоставляет также 2 виртуальных UART порта, которые используются для связи между приложением и ядром. Выглядит все это вот так:

Последовательные порты в OpenCPU приложении
Рисунок 2. Последовательные порты в OpenCPU приложении

Алгоритм работы с UART портом в OpenCPU

  1. Вызываем функцию Ql_UART_Register для инициализации функции обратного вызова.
  2. Вызываем функциюQl_UART_Open для открытия UART порта.
  3. Вызываем функциюQl_UART_Write для записи в порт. Когда количество фактически отправленных байт меньше, чем для отправки и приложение должно остановить передачу данных, приложение получает сообщение события EVENT_UART_READY_TO_WRITE, которое можно обработать в функции обратного вызова. После получения сообщения приложение может продолжить передачу и ранее недоотправленные данные будут отправлены.
  4. Получив в функции обратного вызова сообщение EVENT_UART_READY_TO_READ, разработчик должен прочитать все данные из приемного буфера UART порта с помощью функции Ql_UART_Read, в противном случае не будет приходить новых уведомлений, когда будут приходить новые данные.

Немного более подробно рассмотрим каждую из функций OpenCPU для работы с UART портами.

API для использования UART

Ql_UART_Register

Как уже было сказано выше, эта функция инициализирует функцию обратного вызова UART, которая используется для получения уведомлений о событиях UART от ядра системы.

typedef void (*CallBack_UART_Notify)(Enum_SerialPort port, Enum_UARTEventType event, bool pinLevel, void *customizePara);

s32 Ql_UART_Register (Enum_SerialPort port, CallBack_UART_Notify callback_uart, void * customizePara);

Параметры:

port: Имя порта, например UART_PORT1.

callback_uart:  Указатель на функцию обратного вызова UART.

event: Тип события, одно из значений типа Enum_UARTEventType.

pinLevel: Если тип событияEVENT_UART_RI_IND, EVENT_UART_DCD_IND или EVENT_UART_DTR_IND pinLevel содержит соответствующий логический уровень, в противном случае можно оставить этот параметр без внимания.

customizePara: Настраиваемый параметр, если не используется, то устанавливается в NULL.

Возвращаемое значение:

QL_RET_OK, если функция завершена успешно. В противном случае, код ошибки из ERROR_CODES.

Ql_UART_Open

Функция открывает UART порт. Задача, в которой открывается порт и будет владеть открываемым портом.

s32 Ql_UART_Open (Enum_SerialPort port, u32 baudrate, Enum_FlowCtrl flowCtrl);

Параметры:

port: Имя порта, например UART_PORT1.

baudrate: Физические порты UART поддерживают скорости 75, 150, 300, 600, 1200, 2400, 4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 115200, 230400, 460800. Для виртуальных портов VIRTUAL_PORT1 и VIRTUAL_PORT2 параметр не имеет значения и устанавливается в 0.

flowCtrl: Управление потоком, только UART_PORT1 имеет аппаратную поддержку управления потоком.

Возвращаемое значение:

QL_RET_OK, если функция завершена успешно. В противном случае, код ошибки из ERROR_CODES.

Ql_UART_OpenEx

Функция открывает UART порт с настраиваемыми DCB параметрами. Задача, в которой открывается порт и будет владеть открываемым портом.

s32 Ql_UART_OpenEx (Enum_SerialPort port, ST_UARTDCB *dcb);

Параметры:

port: Имя порта, например UART_PORT1.

dcb: Указатель на структуру настроек DCB, включая baudrate, количество бит данных, стоп-бит, четность, управление потоком. Для виртуальных портов VIRTUAL_PORT1 и VIRTUAL_PORT2 параметр не имеет значения и устанавливается в NULL.

Возвращаемое значение:

QL_RET_OK, если функция завершена успешно. В противном случае, код ошибки из ERROR_CODES.

Ql_UART_Write

Эта функция используется для передачи данных в UART порт. Когда количество фактически отправленных байт меньше, чем для отправки и приложение должно остановить передачу данных, приложение получает сообщение события EVENT_UART_READY_TO_WRITE  в функции обратного вызова. После получения сообщения приложение может продолжить передачу и ранее недоотправленные данные будут отправлены.

s32 Ql_UART_Write (Enum_SerialPort port, u8* data, u32 writeLen);

Параметры:

port: Имя порта, например UART_PORT1.

data: Указатель на буфер отправляемых данных.

writeLen: Длина буфера отправляемых данных. Для виртуальных портов VIRTUAL_PORT1 и VIRTUAL_PORT2 максимальная длина 1023 байта и не может быть изменена программно.

Возвращаемое значение:

Количество переданных байт. Если данные не получилось отправить, возвращается отрицательное число, код ошибки можно увидеть в ERROR_CODES.

Ql_UART_Read

Эта функция считывает данные с указанного порта UART. Если произошел вызов функции обратного вызова UART и тип сообщения EVENT_UART_READ_TO_READ, разработчику необходимо прочитать все данные в буфере данных, что означает, что реальное считываемое значение длины буфера принятых данных меньше, чем подлежащее считыванию значение, в противном случае новых вызовов функции обратного вызова не будет и мы рискуем пропустить новые данные.

s32 Ql_UART_Read (Enum_SerialPort port, u8* data, u32 readLen);

Параметры:

port: Имя порта, например UART_PORT1.

data: Указатель на буфер принимаемых данных.

readLen: Количество байт данных, которые необходимо считать. Максимальная длина буфера приема для физических портов UART равна 3584 байт и 1023 байта для виртуальных портов, причем данный буфер не может быть изменен программно.

Возвращаемое значение:

Если значение больше или равно 0, то чтение прошло успешно и возвращаемое число равно числу фактически принятых байт данных. Если же значение меньше 0, произошел сбой чтения данных.

Ql_UART_SetDCBConfig

Данная функция устанавливает параметры для конкретного UART порта. Функция работает только с физическими портами.

s32 Ql_UART_SetDCBConfig (Enum_SerialPort port, ST_UARTDCB *dcb);

Ниже параметры, которые могут быть установлены:

typedef enum {
    DB_5BIT = 5,
    DB_6BIT,
    DB_7BIT,
    DB_8BIT
} Enum_DataBits;

typedef enum {
    SB_ONE = 1,
    SB_TWO,
    SB_ONE_DOT_FIVE
} Enum_StopBits;

typedef enum {
    PB_NONE = 0,
    PB_ODD,
    PB_EVEN,
    PB_SPACE,
    PB_MARK
} Enum_ParityBits;

typedef enum {
    FC_NONE = 1,	// None Flow Control
    FC_HW,			  // Hardware Flow Control
    FC_SW			   // Software Flow Control
} Enum_FlowCtrl;
typedef struct {
    u32                 baudrate; 
    Enum_DataBits       dataBits;
    Enum_StopBits       stopBits;
    Enum_ParityBits     parity;
    Enum_FlowCtrl       flowCtrl;
}ST_UARTDCB;

Параметры:

port: Имя порта, например UART_PORT1.

dcb: Указатель на структуру настроек DCB, включая baudrate, количество бит данных, стоп-бит, четность, управление потоком.

Возвращаемое значение:

QL_RET_OK, если функция завершена успешно. В противном случае, код ошибки из ERROR_CODES.


Сейчас я перечислил только основные функции для работы с UART в OpenCPU, а их еще есть некоторое количество и более подробно о них можно узнать, конечно же, в SDK в файле ql_uart.h или, прочитав в OpenCPU User Guide.

Пример кода в рабочем приложении

Небольшой кусок кода, включающий использование API функций UART в основной функции приложения и Callback функция UART.

/**
 * @brief Функция обратного вызова UART при событиях типа Enum_UARTEventType.
 *
 * @param [in] port           Имя порта.
 * @param [in] msg            Тип события UART.
 * @param [in] level          Логический уровень при событиях EVENT_UART_RI_IND, EVENT_UART_DCD_IND или EVENT_UART_DTR_IND.
 * @param [in] customizedPara Настраиваемый параметр, если не используется, то устанавливается в NULL.
 */
static void 
callbackUartHandler (Enum_SerialPort port, Enum_UARTEventType msg, bool level, void* customizedPara)
{
    switch (msg)
    {
        /* Сообщение о готовности чтения буфера приема, данные должны быть
         * прочитаны полностью, чтобы получить следующее такое сообщение.
         */
        case EVENT_UART_READY_TO_READ:
            // Заполняем нулями приемный буфер
            Ql_memset (serialRxBuf, 0x00, SERIAL_RX_BUFFER_LEN);

            // Читаем данные в буфер serialRxBuf длиной SERIAL_RX_BUFFER_LEN
            Ql_UART_Read (port, &serialRxBuf, SERIAL_RX_BUFFER_LEN);

            break;

        case EVENT_UART_READY_TO_WRITE:
            break;

        // Изменение уровня DTR, можно разбудить модуль здесь
        case EVENT_UART_DTR_IND:
            if (level == 0)
            {
                APP_DEBUG ("[ INFO ] DTR set to low = %d  Wake !!\r\n", level);
                Ql_SleepDisable();
            } else
            {
                APP_DEBUG ("[ INFO ] DTR set to high = %d  Sleep\r\n", level);
                Ql_SleepEnable();
            }
            break;

        case EVENT_UART_RI_IND:
            break;

        case EVENT_UART_DCD_IND:
            break;

        case EVENT_UART_FE_IND:
            break;

        default:
            break;
    }
}

void 
proc_main_task (s32 taskId)
{
    s32 ret;
    ST_MSG msg;

    // Регистрируем Callback функцию UART1
    ret = Ql_UART_Register (UART_PORT1, callbackUartHandler, NULL);

    if (ret < QL_RET_OK)
        Ql_Debug_Trace ("[ FAIL ] Fail to register serial port, ret=%d\r\n", ret);

    // Открываем порт UART1, здесь 115200 бодрейт порта
    ret = Ql_UART_Open (UART_PORT1, 115200, FC_NONE);

    if (ret < QL_RET_OK)
        Ql_Debug_Trace ("[ FAIL ] Fail to open serial port, ret=%d\r\n", ret);

    // Пишем в UART порт
    ret = Ql_UART_Write (UART_PORT1, &buffer, bufLen);

    if (ret < 0)
        Ql_Debug_Trace ("[ FAIL ] Fail to write to serial port\r\n");
    else
        Ql_Debug_Trace ("[ INFO ] Success write %d bytes to serial port\r\n", ret);


    while (TRUE)
    {
        Ql_OS_GetMessage (&msg);

        switch (msg.message)
        {
            // Здесь ваш обработчик URC сообщений ОС
        }
    }
}

В принципе, код нормально комментирован и особых вопросов вызывать не должен. В любом случае, если возникнут вопросы, пишите в комментариях.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *