【工作筆記】IOT.js適配AWorks平臺通用外設(shè)接口(5):SPI

一铐拐、前言

近期因工作需求學習了一下 IOT.js 和 AWorks 平臺通用外設(shè)接口(包括:ADC徘键、GPIO芳誓、I2C、PWM啊鸭、SPI 和 UART)锹淌,并將它們逐一適配到 IOT.js 中,為后續(xù) AWTK-MVMM 的 JS項目支持平臺外設(shè)調(diào)用奠定基礎(chǔ)赠制,此處做筆記記錄一下赂摆。

備注: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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呻顽,一起剝皮案震驚了整個濱河市雹顺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌廊遍,老刑警劉巖嬉愧,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喉前,居然都是意外死亡没酣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門卵迂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裕便,“玉大人,你說我怎么就攤上這事狭握∩两穑” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵论颅,是天一觀的道長哎垦。 經(jīng)常有香客問我,道長恃疯,這世上最難降的妖魔是什么漏设? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮今妄,結(jié)果婚禮上郑口,老公的妹妹穿的比我還像新娘。我一直安慰自己盾鳞,他們只是感情好犬性,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腾仅,像睡著了一般乒裆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上推励,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天鹤耍,我揣著相機與錄音,去河邊找鬼验辞。 笑死稿黄,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的跌造。 我是一名探鬼主播杆怕,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壳贪!你這毒婦竟也來了财著?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤撑碴,失蹤者是張志新(化名)和其女友劉穎撑教,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體醉拓,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡伟姐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了亿卤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愤兵。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖排吴,靈堂內(nèi)的尸體忽然破棺而出秆乳,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布屹堰,位于F島的核電站肛冶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扯键。R本人自食惡果不足惜睦袖,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荣刑。 院中可真熱鬧馅笙,春花似錦、人聲如沸厉亏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爱只。三九已至皿淋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虱颗,已是汗流浹背沥匈。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忘渔,地道東北人高帖。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像畦粮,于是被迫代替她去往敵國和親散址。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容