一铐拐、前言
近期因工作需求學習了一下 IOT.js 和 AWorks 平臺通用外設(shè)接口(包括:ADC徘键、GPIO芳誓、I2C、PWM啊鸭、SPI 和 UART)锹淌,并將它們逐一適配到 IOT.js 中,為后續(xù) AWTK-MVMM 的 JS項目支持平臺外設(shè)調(diào)用奠定基礎(chǔ)赠制,此處做筆記記錄一下赂摆。
- 【工作筆記】IOT.js適配AWorks平臺通用外設(shè)接口(1):ADC;
- 【工作筆記】IOT.js適配AWorks平臺通用外設(shè)接口(2):GPIO钟些;
- 【工作筆記】IOT.js適配AWorks平臺通用外設(shè)接口(3):I2C烟号;
- 【工作筆記】IOT.js適配AWorks平臺通用外設(shè)接口(4):PWM;
- 【工作筆記】IOT.js適配AWorks平臺通用外設(shè)接口(5):SPI政恍;
- 【工作筆記】IOT.js適配AWorks平臺通用外設(shè)接口(6):UART汪拥;
備注:IOT.js 和 AWorks 的相關(guān)介紹請看第一篇 ADC 適配筆記。
二篙耗、SPI
2.1 SPI總線
SPI(Serial Peripheral Interface)是一種全雙工同步串行通信接口迫筑,常用于短距離高速通信,其數(shù)據(jù)傳輸速率通匙谕洌可達到幾M甚至幾十M脯燃。SPI通信采用主/從結(jié)構(gòu),主/從雙方通信時蒙保,需要使用到4根信號線:SCLK辕棚、MOSI、MISO邓厕、CS逝嚎。其典型的連接示意圖如下:
[圖片上傳失敗...(image-24da6e-1652796840636)]
- SCLK:時鐘信號,由主機設(shè)備產(chǎn)生详恼。
- MOSI:主機數(shù)據(jù)輸出补君,從機數(shù)據(jù)輸入。
- MISO:從機數(shù)據(jù)輸出单雾,主機數(shù)據(jù)輸入赚哗。
- CS:片選信號她紫,由主設(shè)備控制硅堆。
數(shù)據(jù)傳輸是由主機發(fā)起,主機在串行數(shù)據(jù)傳輸前驅(qū)動CS信號贿讹,使之變?yōu)橛行顟B(tài)(通常情況下渐逃,有效狀態(tài)為低電平),接著民褂,在SCLK上輸出時鐘信號茄菊,在時鐘信號的同步下疯潭,每個時鐘傳輸一位數(shù)據(jù),主機數(shù)據(jù)通過MOSI傳輸至從機面殖,從機數(shù)據(jù)通過MISO傳輸至主機竖哩,數(shù)據(jù)傳輸完畢后,主機釋放CS信號脊僚,使之變?yōu)闊o效狀態(tài)相叁,一次數(shù)據(jù)傳輸完成。
一個主機可以連接多個從機辽幌,多個從機共同使用SCLK增淹、MOSI、MISO三根信號線乌企,每個從機的片選信號 CS 是獨立的虑润,因此,若主機連接多個從機加酵,就需要多個片選控制引腳拳喻。當一個主機連接多個從機時,同一時刻最多只能使一個片選信號有效猪腕,以選擇一個確定的從機作為數(shù)據(jù)通信的目標對象舞蔽,也就是說,在某一時刻码撰,最多只能激活尋址一個從機渗柿,以使各個從機之間相互獨立的使用,互不干擾脖岛。注意沒在單個通信網(wǎng)絡(luò)中朵栖,可以有多個從機,但有且只能有一個主機柴梆。
2.2 SPI接口
絕大部分情況下陨溅,MCU都作為SPI主機與SPI從機器件通信,因此這里僅介紹 AWorks中將MCU作為SPI主機的相關(guān)接口:
- aw_spi_mkdev:初始化SPI從機實例绍在。
- aw_spi_setup:設(shè)置SPI從機實例门扇。
- aw_spi_write_then_read:SPI先寫后讀。
- aw_spi_write_then_write:執(zhí)行SPI兩次寫偿渡。
三臼寄、適配過程
3.1 AWorks演示代碼
先來看看這些SPI相關(guān)接口的基本用法,我們在底板上跑一下簡單的例程溜宽。
步驟一:外設(shè)使能吉拳,在AWorks工程配置文件 aw_prj_params.h
中開啟以下宏定義使能SPI:
#define AW_DEV_IMX1050_LPSPI3 /**< \brief iMX1050 LPSPI3 (SPI3) */
步驟二:到外設(shè)文件中查看設(shè)備對應的引腳,比如這里查看 awbl_hwconf_imx1050_lpspi3.h
文件适揉,可以看到該SPI3設(shè)備的CS留攒、MISO煤惩、MOSI、SCK分別使用GPIO1_28炼邀、GPIO1_29魄揉、GPIO1_30、GPIO1_31引腳拭宁,分別對應底板上的ss什猖、MISO、MOSI红淡、CLK引腳不狮,我們需確定這些引腳能正常使用。
步驟三:編寫例程在旱,測試SPI主機模式的基本讀寫功能摇零。示例代碼如下:
#include "aworks.h" /* 此頭文件必須被首先包含 */
#include "aw_delay.h" /* 延時服務 */
#include "aw_spi.h"
#include "aw_gpio.h"
#include "aw_vdebug.h"
#include "aw_demo_config.h"
#define HARD_CS_CFG 1
//#define SOFT_CS_CFG 1 /* 使用軟件CS請打開spi配置文件,關(guān)閉硬件CS模式*/
/** \brief SPI1的配置 */
#define SPI_DEV_BUSID DE_SPI_DEV_BUSID /**< \brief SPI總線 */
#define SPI_DEV_MODE AW_SPI_CPHA /**< \brief SPI模式 */
#define SPI_DEV_SPEED 2000000 /**< \brief SPI速度 */
#define SPI_DEV_BPW 8 /**< \brief SPI數(shù)據(jù)位數(shù)桶蝎,取值只能是8驻仅、16、32 */
#define SPI_TEST_LEN 16 /**< \brief SPI收發(fā)數(shù)據(jù)量 */
#define SPI_CS_PIN DE_SPI_CS_PIN /**< \brief SPI片選引腳登渣,根據(jù)具體平臺修改 */
struct aw_spi_message spi_msg;
struct aw_spi_transfer spi_trans;
#if HARD_CS_CFG
void pfunc_cs (int state){
return ;
}
#endif
/*
* \brief 測試SPI主機模式的基本讀寫功能
*/
int main() {
aw_spi_device_t spi_dev;
int ret;
int i;
#if SPI_DEV_BPW == 8
uint8_t read_buf[SPI_TEST_LEN] = {0};
uint8_t write_buf[SPI_TEST_LEN] = {0};
#elif SPI_DEV_BPW == 16
uint16_t read_buf[SPI_TEST_LEN] = {0};
uint16_t write_buf[SPI_TEST_LEN] = {0};
#elif SPI_DEV_BPW == 32
uint32_t read_buf[SPI_TEST_LEN] = {0};
uint32_t write_buf[SPI_TEST_LEN] = {0};
#endif
#if SOFT_CS_CFG
int cs_pin = SPI_CS_PIN;
/* 配置SPI之前需先申請CS引腳 */
aw_gpio_pin_request("spi_demo_cs", &cs_pin, 1);
#endif
/* 生成 SPI FLASH 設(shè)備 */
aw_spi_mkdev(&spi_dev,
SPI_DEV_BUSID, /* 位于哪條SPI總線上 */
SPI_DEV_BPW, /* 字大小 */
SPI_DEV_MODE, /* SPI 模式 */
SPI_DEV_SPEED, /* 支持的最大速度 */
#if HARD_CS_CFG
NULL, /* 片選引腳 */
pfunc_cs); /* 無自定義的片選控制函數(shù) */
#else
cs_pin, /* 片選引腳 */
NULL); /* 無自定義的片選控制函數(shù) */
#endif
if (aw_spi_setup(&spi_dev) != AW_OK) {
aw_kprintf("aw_spi_setup fail!\r\n");
goto _exit ;
}
/* 設(shè)置發(fā)送數(shù)據(jù) */
for (i = 0; i < SPI_TEST_LEN; i++) {
write_buf[i] = i;
}
memset(read_buf,0,SPI_TEST_LEN);
while(1){
/* 傳輸數(shù)據(jù) */
ret = aw_spi_write_and_read (&spi_dev, write_buf, read_buf, sizeof(write_buf));
if (ret != AW_OK) {
aw_kprintf("__spi_trans fail!\r\n");
goto _exit ;
}
aw_kprintf("write_buf:");
for(i = 0; i < SPI_TEST_LEN;i++){
aw_kprintf("%d ",write_buf[i]);
}
aw_kprintf("\r\n");
aw_kprintf("read_buf:");
for(i = 0; i < SPI_TEST_LEN;i++){
aw_kprintf("%d ",read_buf[i]);
}
aw_kprintf("\r\n");
aw_mdelay(500);
}
_exit:
#if SOFT_CS_CFG
/* 測試完成后釋放CS引腳 */
aw_gpio_pin_release(&cs_pin, 1);
#endif
return 0;
}
輸出結(jié)果:
write_buf:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
read_buf:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
3.2 C語言適配層
在 IOT.js 中噪服,適配某個平臺的外設(shè)通常需要實現(xiàn) src/modules/iotjs_module_xxx.h
文件中的接口,比如這里我們需要實現(xiàn) iotjs_module_spi.h
中的相關(guān)接口:
#ifndef IOTJS_MODULE_SPI_H
#define IOTJS_MODULE_SPI_H
#include "iotjs_def.h"
#include "iotjs_module_buffer.h"
#include "iotjs_module_periph_common.h"
typedef enum {
kSpiMode_0,
kSpiMode_1,
kSpiMode_2,
kSpiMode_3,
__kSpiModeMax
} SpiMode;
typedef enum { kSpiCsNone, kSpiCsHigh, __kSpiCsMax } SpiChipSelect;
typedef enum { kSpiOrderMsb, kSpiOrderLsb, __kSpiOrderMax } SpiOrder;
// Forward declaration of platform data. These are only used by platform code.
// Generic SPI module never dereferences platform data pointer.
typedef struct iotjs_spi_platform_data_s iotjs_spi_platform_data_t;
// This SPI class provides interfaces for SPI operation.
typedef struct {
jerry_value_t jobject;
iotjs_spi_platform_data_t* platform_data;
SpiMode mode;
SpiChipSelect chip_select;
SpiOrder bit_order;
uint8_t bits_per_word;
uint16_t delay;
uint32_t max_speed;
bool loopback;
// SPI buffer
char* tx_buf_data;
char* rx_buf_data;
uint8_t buf_len;
} iotjs_spi_t;
jerry_value_t iotjs_spi_set_platform_config(iotjs_spi_t* spi,
const jerry_value_t jconfig);
bool iotjs_spi_open(iotjs_spi_t* spi);
bool iotjs_spi_transfer(iotjs_spi_t* spi);
bool iotjs_spi_close(iotjs_spi_t* spi);
// Platform-related functions; they are implemented
// by platform code (i.e.: linux, nuttx, tizen).
void iotjs_spi_create_platform_data(iotjs_spi_t* spi);
void iotjs_spi_destroy_platform_data(iotjs_spi_platform_data_t* pdata);
#endif /* IOTJS_MODULE_SPI_H */
適配層(src/modules/aworks/iotjs_module_spi-aworks.c
)代碼如下:
#if !defined(WITH_AWORKS)
#error "Module __FILE__ is for AWorks only"
#endif
#include "iotjs_def.h"
#include "aw_spi.h"
#include "modules/iotjs_module_spi.h"
struct iotjs_spi_platform_data_s {
int bus;
aw_spi_device_t spi_dev;
};
static int mode_to_constant(SpiMode mode) {
switch (mode) {
case kSpiMode_0:
return AW_SPI_MODE_0;
case kSpiMode_1:
return AW_SPI_MODE_1;
case kSpiMode_2:
return AW_SPI_MODE_2;
case kSpiMode_3:
return AW_SPI_MODE_3;
}
return -1;
}
static int clip_select_to_constant(SpiOrder order) {
switch (order) {
case kSpiCsNone:
return 0;
case kSpiCsHigh:
return AW_SPI_CS_HIGH;
}
return -1;
}
static int order_to_constant(SpiOrder order) {
switch (order) {
case kSpiOrderMsb:
return 0;
case kSpiOrderLsb:
return AW_SPI_LSB_FIRST;
}
return -1;
}
static void pfunc_cs(int state) {
return;
}
void iotjs_spi_create_platform_data(iotjs_spi_t* spi) {
spi->platform_data = IOTJS_ALLOC(iotjs_spi_platform_data_t);
spi->platform_data->bus = -1;
}
void iotjs_spi_destroy_platform_data(iotjs_spi_platform_data_t* platform_data) {
IOTJS_ASSERT(platform_data);
IOTJS_RELEASE(platform_data);
}
jerry_value_t iotjs_spi_set_platform_config(iotjs_spi_t* spi,
const jerry_value_t jconfig) {
iotjs_spi_platform_data_t* platform_data = spi->platform_data;
JS_GET_REQUIRED_CONF_VALUE(jconfig, platform_data->bus,
IOTJS_MAGIC_STRING_BUS, number);
return jerry_create_undefined();
}
bool iotjs_spi_open(iotjs_spi_t* spi) {
aw_err_t ret;
int mode;
int order;
int clip_select;
uint16_t flags;
iotjs_spi_platform_data_t* platform_data = spi->platform_data;
mode = mode_to_constant(spi->mode);
order = order_to_constant(spi->bit_order);
clip_select = order_to_constant(spi->chip_select);
if (mode < 0 || order < 0 || clip_select < 0) {
DLOG("%s: spi open error: bad config param.", __func__);
return false;
}
flags = mode | order | clip_select;
aw_spi_mkdev(&platform_data->spi_dev, /* SPI設(shè)備實例 */
platform_data->bus, /* SPI總線編號 */
spi->bits_per_word, /* 數(shù)據(jù)位寬 */
flags, /* 設(shè)備模式標志 */
spi->max_speed, /* 支持最大速度 */
0, /* 片選引腳(這里使用硬件片選) */
pfunc_cs /* 片選控制函數(shù)(為NULL時使用軟件片選) */
);
ret = aw_spi_setup(&platform_data->spi_dev);
if (ret != AW_OK) {
memset(&platform_data->spi_dev, 0x00, sizeof(aw_spi_device_t));
DLOG("%s: spi setup error(%d)", __func__, ret);
return false;
}
DDLOG(
"SPI Options \n mode: %d\n chipSelect: %d\n bitOrder: %d\n "
"maxSpeed: %d\n bitPerWord: %d\n",
spi->mode, spi->chip_select, spi->bit_order, spi->max_speed,
spi->bits_per_word);
return true;
}
bool iotjs_spi_transfer(iotjs_spi_t* spi) {
aw_err_t ret;
iotjs_spi_platform_data_t* platform_data = spi->platform_data;
ret = aw_spi_write_and_read(&platform_data->spi_dev, (void*)spi->tx_buf_data,
(void*)spi->rx_buf_data, spi->buf_len);
if (ret != AW_OK) {
DDLOG("%s - transfer failed: %d", __func__, ret);
return false;
}
return true;
}
bool iotjs_spi_close(iotjs_spi_t* spi) {
iotjs_spi_platform_data_t* platform_data = spi->platform_data;
platform_data->bus = -1;
memset(&platform_data->spi_dev, 0x00, sizeof(aw_spi_device_t));
return true;
}
3.2 JS測試代碼
適配好后胜茧,我們編寫 JS 代碼測試一下:
var spi = require('spi');
var configuration = {
bus: 2, /* 使用SPI3的總線編號 */
mode: spi.MODE[1], /* 模式一 */
maxSpeed: 2000000, /* 單位:Hz粘优,表示最大速度2M */
bitsPerWord: 8, /* 數(shù)據(jù)位寬(目前僅支持8位) */
};
console.log('test start');
var spiObj = spi.openSync(configuration);
var data = 'Hello Iotjs!'; /* 發(fā)送字符串 */
// var data = [0x90, 0x00, 0x00, 0x00, 0xff, 0xff]; /* 發(fā)送數(shù)組(該指令用來獲取從機ID,需要配套的SPI從機芯片) */
var tx = new Buffer(data);
var loopCnt = 1;
var loop = setInterval(function () {
var rx = spiObj.transferSync(tx);
var len = data.length;
console.log('data.length: ', len);
var value = '';
for (var i = 0; i < len; i++) {
// console.log(rx[i]); /* 打印讀取數(shù)據(jù)的原始值 */
value += String.fromCharCode(rx[i]);
}
console.log(value);
if (--loopCnt <= 0) {
clearInterval(loop);
spiObj.closeSync();
console.log('test complete');
}
}, 1000);
輸出結(jié)果:
test start
data.length:12
Hello Iotjs!
test complete