В этой статье рассмотрю работу с последовательными портами в 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):
Тут стоит еще отметить, что OpenCPU предоставляет также 2 виртуальных UART порта, которые используются для связи между приложением и ядром. Выглядит все это вот так:
Алгоритм работы с UART портом в OpenCPU
- Вызываем функцию
Ql_UART_Register
для инициализации функции обратного вызова. - Вызываем функцию
Ql_UART_Open
для открытия UART порта. - Вызываем функцию
Ql_UART_Write
для записи в порт. Когда количество фактически отправленных байт меньше, чем для отправки и приложение должно остановить передачу данных, приложение получает сообщение событияEVENT_UART_READY_TO_WRITE
, которое можно обработать в функции обратного вызова. После получения сообщения приложение может продолжить передачу и ранее недоотправленные данные будут отправлены. - Получив в функции обратного вызова сообщение
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 сообщений ОС } } }
В принципе, код нормально комментирован и особых вопросов вызывать не должен. В любом случае, если возникнут вопросы, пишите в комментариях.
А не подскажите на какой скорости работает отладочный порт вывод в который осуществляется через Ql_Debug_Trace()? И осуществляется ли? Нужно ли как-то инициализировать это?
Никаких настроек не нужно, порт работает на скорости 115200 бод.
А вот что-то не работает 🙁
Причем обычные примеры из коробки. Пару символов каких-то выплевывает и все.
ql_trace.h подключен в проекте?
Как ни странно да…