В этой статье рассмотрю работу с последовательными портами в 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 подключен в проекте?
Как ни странно да…