一、前言
近期因工作需求學(xué)習(xí)了一下 IOT.js 和 AWorks 平臺(tái)通用外設(shè)接口(包括:ADC、GPIO、I2C、PWM、SPI 和 UART),并將它們逐一適配到 IOT.js 中,為后續(xù) AWTK-MVMM 的 JS項(xiàng)目支持平臺(tái)外設(shè)調(diào)用奠定基礎(chǔ),此處做筆記記錄一下。
- 【工作筆記】IOT.js適配AWorks平臺(tái)通用外設(shè)接口(1):ADC;
- 【工作筆記】IOT.js適配AWorks平臺(tái)通用外設(shè)接口(2):GPIO;
- 【工作筆記】IOT.js適配AWorks平臺(tái)通用外設(shè)接口(3):I2C;
- 【工作筆記】IOT.js適配AWorks平臺(tái)通用外設(shè)接口(4):PWM;
- 【工作筆記】IOT.js適配AWorks平臺(tái)通用外設(shè)接口(5):SPI;
- 【工作筆記】IOT.js適配AWorks平臺(tái)通用外設(shè)接口(6):UART;
備注:IOT.js 和 AWorks 的相關(guān)介紹請(qǐng)看第一篇 ADC 適配筆記。
二、UART
2.1 UART總線
UART(Universal Asynchronous Receiver/Transmitter)是一種通用異步收發(fā)傳輸器,其使用串行的方式在雙機(jī)之間進(jìn)行數(shù)據(jù)交換,實(shí)現(xiàn)全雙工通信,數(shù)據(jù)引腳僅包含用于接收數(shù)據(jù)的RXD和用于發(fā)送數(shù)據(jù)的TXD。數(shù)據(jù)在數(shù)據(jù)線上按一位一位的串行傳輸,要正確解析這些數(shù)據(jù),必須遵循UART協(xié)議,作為了解,這里僅簡(jiǎn)要講述幾個(gè)關(guān)鍵的概念:
波特率:它決定了數(shù)據(jù)傳輸?shù)乃俾?,其表示每秒傳?shù)據(jù)的位數(shù),值越大,數(shù)據(jù)通信的速率越高,數(shù)據(jù)傳輸?shù)迷娇?。常見的波特率有?800、9600、14400、19200、38400、115200等等,若波特率為115200,則表示每秒鐘可以傳輸115200位(bit)數(shù)據(jù)。
空閑位:數(shù)據(jù)線上沒有數(shù)據(jù)傳輸時(shí),數(shù)據(jù)線處于空閑狀態(tài)??臻e狀態(tài)的電平邏輯為”1“。
起使位:它表示一幀數(shù)據(jù)傳輸?shù)拈_始,起使位的電平邏輯是”0“。
數(shù)據(jù)包:緊接起始位后,即位實(shí)際通信傳輸?shù)臄?shù)據(jù),數(shù)據(jù)的位數(shù)可以是5、6、7、8等,數(shù)據(jù)傳輸時(shí),從最低位開始依次傳輸。
奇偶校驗(yàn)位:奇偶校驗(yàn)位用于接受方對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),即使發(fā)現(xiàn)由于通信故障等問題造成的錯(cuò)誤數(shù)據(jù),它是可選的,可以不適用奇偶校驗(yàn)位。
停止位:它表示一幀數(shù)據(jù)的結(jié)束,其電平邏輯為”1“,其寬度可以是1位、1.5位、2位。即其持續(xù)的時(shí)間為位數(shù)乘以傳輸一位的時(shí)間(由波特率決定),例如波特率位115200,則傳輸一位的時(shí)間為1/115200秒,約為8.68us。若停止位的寬度為1.5位,則表示停止位持續(xù)時(shí)間位:1.5 * 8.68 us,約等于 13 us。
常見的幀格式位:1位起始位,8位數(shù)據(jù)位,無校驗(yàn),1位停止位。由于起使位的寬度恒為1位,不會(huì)變化,而數(shù)據(jù)位,校驗(yàn)位和停止位都是可變的,因此,往往在描述串口通信協(xié)議時(shí),都只是描述其波特率、數(shù)據(jù)位,校驗(yàn)位和停止位,不再單獨(dú)說明起使位。
注意:通信雙方必須使用完全相同的協(xié)議,包括波特率、起始位、數(shù)據(jù)位、停止位等。如果協(xié)議不一致,則通信數(shù)據(jù)會(huì)錯(cuò)亂,不能正常通信。再通信中,若出現(xiàn)亂碼的情況,應(yīng)該首先檢查通信雙方所使用的協(xié)議是否一致。
2.2 串行接口
在AWorks中,定義了通用的串行接口,可以使用串行接口操作UART,實(shí)現(xiàn)數(shù)據(jù)的收發(fā),相關(guān)接口如下:
- aw_serial_ioctl:UART控制。
- aw_serial_write:發(fā)送數(shù)據(jù)。
- aw_serial_read:接收數(shù)據(jù)。
三、適配過程
3.1 AWorks演示代碼
先來看看這些UART相關(guān)接口的基本用法,我們?cè)诘装迳吓芤幌潞?jiǎn)單的例程。
步驟一:外設(shè)使能,在AWorks工程配置文件 aw_prj_params.h 中開啟以下宏定義使能COM0串口:
#define AW_DEV_IMX1050_LPUART1 /**< \brief iMX1050 LPUART1 (COM0) */
步驟二:到外設(shè)文件中查看設(shè)備對(duì)應(yīng)的引腳,比如這里查看 awbl_hwconf_imx1050_lpuart1.h 文件,可以看到該設(shè)備的使用的引腳為GPIO1_12和GPIO1_13引腳,在底板上分別對(duì)應(yīng)RXD和TXD引腳,我們需確定這些引腳能正常使用。
步驟三:編寫例程,測(cè)試COM0串口的讀寫功能。示例代碼如下:
#include "aworks.h"
#include "aw_delay.h"
#include "aw_serial.h"
#include "aw_ioctl.h"
int main() {
#define TEST_SERIAL_NUM COM0
char buf[32];
int len = 0;
int i = 0;
/* 串口初始化配置 波特率115200 */
aw_serial_ioctl(TEST_SERIAL_NUM, SIO_BAUD_SET, (void *)115200);
/* 串口參數(shù) :8個(gè)數(shù)據(jù)位 1個(gè)停止位,無奇偶校驗(yàn) */
aw_serial_ioctl(TEST_SERIAL_NUM, SIO_HW_OPTS_SET, (void *)(CS8 | CLOCAL | CREAD));
/* 串口模式 :查詢模式 發(fā)送字符串 */
aw_serial_ioctl(TEST_SERIAL_NUM, SIO_MODE_SET, (void *)SIO_MODE_POLL);
aw_serial_poll_write(TEST_SERIAL_NUM, "Hello,Enter Serial Poll Mode:\r\n", 32);
for(i = 0; i < 10; i++) {
len = aw_serial_poll_read(TEST_SERIAL_NUM, buf, 10);
if (len > 0) {
aw_serial_poll_write(TEST_SERIAL_NUM, buf, len);
}
aw_serial_poll_write(TEST_SERIAL_NUM, "\r\n", len);
}
/*
* 再次配置串口模式 :中斷模式
*
*/
// 清空串口輸入輸出緩沖區(qū)
aw_serial_ioctl(TEST_SERIAL_NUM, AW_FIOFLUSH, NULL);
// 設(shè)置串口讀取超時(shí)時(shí)間
aw_serial_ioctl(TEST_SERIAL_NUM, AW_TIOCRDTIMEOUT, (void *)10);
// 設(shè)置為中斷模式
aw_serial_ioctl(TEST_SERIAL_NUM, SIO_MODE_SET, (void *)SIO_MODE_INT);
// 發(fā)送數(shù)據(jù)
aw_serial_write(TEST_SERIAL_NUM, "Hello,Enter Serial INT Mode:\r\n", 32);
for(i = 0; i < 10; i++) {
// 讀取數(shù)據(jù)
len = aw_serial_read(TEST_SERIAL_NUM, buf, 10);
if (len > 0) {
aw_serial_write(TEST_SERIAL_NUM, buf, len);
}
aw_serial_write(TEST_SERIAL_NUM, "\r\n", len);
}
for (;;) {
aw_mdelay(1000);
}
return 0;
}
3.2 C語言適配層
在 IOT.js 中,適配某個(gè)平臺(tái)的外設(shè)通常需要實(shí)現(xiàn) src/modules/iotjs_module_xxx.h 文件中的接口,比如這里我們需要實(shí)現(xiàn) iotjs_module_uart.h 中的相關(guān)接口:
#ifndef IOTJS_MODULE_UART_H
#define IOTJS_MODULE_UART_H
#include "iotjs_def.h"
#include "iotjs_module_periph_common.h"
#ifndef UART_WRITE_BUFFER_SIZE
#define UART_WRITE_BUFFER_SIZE 512
#endif
typedef struct iotjs_uart_platform_data_s iotjs_uart_platform_data_t;
typedef struct {
int device_fd;
unsigned baud_rate;
uint8_t data_bits;
iotjs_string_t buf_data;
unsigned buf_len;
char* buf;
iotjs_uart_platform_data_t* platform_data;
} iotjs_uart_t;
void iotjs_uart_handle_close_cb(uv_handle_t* handle);
void iotjs_uart_register_read_cb(uv_poll_t* uart_poll_handle);
void iotjs_uart_create_platform_data(iotjs_uart_t* uart);
jerry_value_t iotjs_uart_set_platform_config(iotjs_uart_t* uart,
const jerry_value_t jconfig);
void iotjs_uart_destroy_platform_data(iotjs_uart_platform_data_t* pdata);
bool iotjs_uart_open(uv_handle_t* uart_poll_handle);
bool iotjs_uart_write(uv_handle_t* uart_poll_handle);
#endif /* IOTJS_MODULE_UART_H */
適配層(src/modules/aworks/iotjs_module_uart-aworks.c)代碼如下:
#if !defined(WITH_AWORKS)
#error "Module __FILE__ is for AWorks only"
#endif
#include "uv.h"
#include "iotjs_def.h"
#include "iotjs_uv_handle.h"
#include "modules/iotjs_module_uart.h"
#include "aw_serial.h"
#include "aw_delay.h"
#include "aw_ioctl.h"
#ifndef AWORKS_UART_WRITE_BUFFER_SIZE
#define AWORKS_UART_WRITE_BUFFER_SIZE 127
#endif
struct iotjs_uart_platform_data_s {
iotjs_string_t device;
uint32_t wait_time;
int32_t buf_len;
uv_mutex_t mutex;
char buf[UART_WRITE_BUFFER_SIZE];
};
static int device_to_constant(const char* device) {
int ret = -1;
if (!strcmp(device, "COM0")) {
ret = COM0;
} else if (!strcmp(device, "COM1")) {
ret = COM1;
} else if (!strcmp(device, "COM2")) {
ret = COM2;
} else if (!strcmp(device, "COM3")) {
ret = COM3;
} else if (!strcmp(device, "COM4")) {
ret = COM4;
} else if (!strcmp(device, "COM5")) {
ret = COM5;
} else if (!strcmp(device, "COM6")) {
ret = COM6;
} else if (!strcmp(device, "COM7")) {
ret = COM7;
} else if (!strcmp(device, "COM8")) {
ret = COM8;
} else if (!strcmp(device, "COM9")) {
ret = COM9;
}
return ret;
}
static int databits_to_constant(uint8_t dataBits) {
switch (dataBits) {
case 8:
return CS8;
case 7:
return CS7;
case 6:
return CS6;
case 5:
return CS5;
}
return -1;
}
void iotjs_uart_create_platform_data(iotjs_uart_t* uart) {
uart->platform_data = IOTJS_ALLOC(iotjs_uart_platform_data_t);
}
void iotjs_uart_destroy_platform_data(iotjs_uart_platform_data_t* pdata) {
IOTJS_ASSERT(pdata);
uv_mutex_destroy(&pdata->mutex);
iotjs_string_destroy(&pdata->device);
IOTJS_RELEASE(pdata);
}
jerry_value_t iotjs_uart_set_platform_config(iotjs_uart_t* uart,
const jerry_value_t jconfig) {
JS_GET_REQUIRED_CONF_VALUE(jconfig, uart->platform_data->device,
IOTJS_MAGIC_STRING_DEVICE, string);
return jerry_create_undefined();
}
static int uv_poll_uart_try_read(uv_poll_t* handle) {
char buf[512];
int32_t len = 0, buf_len = 0, buf_start = 0;
iotjs_uart_t* uart = (iotjs_uart_t*)IOTJS_UV_HANDLE_EXTRA_DATA(handle);
uv_mutex_lock(&uart->platform_data->mutex);
buf_len = min(UART_WRITE_BUFFER_SIZE - uart->platform_data->buf_len, sizeof(buf));
uv_mutex_unlock(&uart->platform_data->mutex);
if (buf_len > 0) {
len = aw_serial_read(uart->device_fd, buf, buf_len);
if (len > 0) {
uv_mutex_lock(&uart->platform_data->mutex);
buf_start = uart->platform_data->buf_len;
assert(buf_start + len < UART_WRITE_BUFFER_SIZE);
memcpy(uart->platform_data->buf + buf_start, buf, len);
uart->platform_data->buf_len += len;
uv_mutex_unlock(&uart->platform_data->mutex);
} else {
aw_mdelay(uart->platform_data->wait_time);
}
} else {
aw_mdelay(1);
}
if (len > 0) {
return 0;
} else {
return 1;
}
}
bool iotjs_uart_open(uv_handle_t* uart_poll_handle) {
aw_err_t ret;
struct aw_serial_timeout timeout;
uv_poll_t* uv_poll_handle = (uv_poll_t*)uart_poll_handle;
iotjs_uart_t* uart =
(iotjs_uart_t*)IOTJS_UV_HANDLE_EXTRA_DATA(uart_poll_handle);
int com = device_to_constant(iotjs_string_data(&uart->platform_data->device));
int data_bits = databits_to_constant(uart->data_bits);
if (com < 0 || data_bits < 0) {
DLOG("%s: serial port number error or data_bits error(%d)", __func__, com);
return false;
}
/* 設(shè)置串口波特率 */
ret = aw_serial_ioctl(com, SIO_BAUD_SET, (void*)uart->baud_rate);
if (ret != AW_OK) {
DLOG("%s: cannot set baud rate(%d)", __func__, ret);
return false;
}
/* 設(shè)置數(shù)據(jù)位數(shù) */
ret = aw_serial_ioctl(com, SIO_HW_OPTS_SET, (void*)data_bits);
if (ret != AW_OK) {
DLOG("%s: cannot set data bits(%d)", __func__, ret);
return false;
}
timeout.rd_timeout = aw_ms_to_ticks(100); /* 讀總超時(shí)為100ms */
timeout.rd_interval_timeout = aw_ms_to_ticks(100); /* 碼間超時(shí)為100ms */
ret = aw_serial_timeout_set(com, &timeout);
if (ret != AW_OK) {
DLOG("%s: cannot set timeout(%d)", __func__, ret);
return false;
}
uart->device_fd = com;
iotjs_uart_register_read_cb(uv_poll_handle);
uart->platform_data->buf_len = 0;
uv_mutex_init(&uart->platform_data->mutex);
memset(uart->platform_data->buf, 0x0, sizeof(uart->platform_data->buf));
uv_poll_set_try_poll_function(uv_poll_handle, uv_poll_uart_try_read);
uart->platform_data->wait_time = AWORKS_UART_WRITE_BUFFER_SIZE / (uart->baud_rate / 10 / 1000 + 1) / 2;
return true;
}
bool iotjs_uart_write(uv_handle_t* uart_poll_handle) {
int ret = 0;
iotjs_uart_t* uart =
(iotjs_uart_t*)IOTJS_UV_HANDLE_EXTRA_DATA(uart_poll_handle);
int com = device_to_constant(iotjs_string_data(&uart->platform_data->device));
const char* buf_data = iotjs_string_data(&uart->buf_data);
DDDLOG("%s - data: %s", __func__, buf_data);
ret = aw_serial_write(com, buf_data, uart->buf_len);
if (ret < 0) {
DLOG("%s: write data failed(%d)", __func__, ret);
return false;
}
return true;
}
void iotjs_uart_handle_close_cb(uv_handle_t* uart_poll_handle) {
iotjs_uart_t* uart =
(iotjs_uart_t*)IOTJS_UV_HANDLE_EXTRA_DATA(uart_poll_handle);
iotjs_uart_destroy_platform_data(uart->platform_data);
}
int uart_read(iotjs_uart_t* uart, void* dst, unsigned int n) {
int32_t buf_len = 0;
uv_mutex_lock(&uart->platform_data->mutex);
if (uart->platform_data->buf_len > 0) {
buf_len = min(n, uart->platform_data->buf_len);
assert(buf_len >= 0);
memcpy(dst, uart->platform_data->buf, buf_len);
uart->platform_data->buf_len -= buf_len;
if (uart->platform_data->buf_len > 0) {
memcpy(uart->platform_data->buf, uart->platform_data->buf + buf_len, uart->platform_data->buf_len);
}
assert(uart->platform_data->buf_len >= 0);
}
uv_mutex_unlock(&uart->platform_data->mutex);
return buf_len;
}
3.2 JS測(cè)試代碼
適配好后,我們編寫 JS 代碼測(cè)試一下:
var uart = require('uart');
var configuration = {
device: 'COM2', /* 串口3使用GPIO1_22和GPIO1_23引腳,在底板上分別對(duì)應(yīng)RX3和TX3引腳 */
baudRate: 115200,
dataBits: 8,
};
var read = 0;
var write = 0;
var serial = uart.open(configuration, function (err) {
console.log('open done');
serial.on('data', function (data) {
console.log('read result: ' + data.toString());
read = 1;
if (read && write) {
serial.close(function (err) {
if (err) {
console.log('Have an error: ' + err.message);
}
console.log('on read data callback close done');
});
}
});
serial.write('Hello there?\n\r', function (err) {
console.log('write done');
write = 1;
if (read && write) {
console.log('on read data callback close start');
serial.close(function (err) {
if (err) {
console.log('Have an error: ' + err.message);
}
console.log('on write data callback close done');
});
}
});
});
輸出結(jié)果:
open done
write done
read result: Hello there?
on read data callback close done