數位時鐘 - Part 1

簡介

本教學文件使用 LinkIt 7687 開發板的功能來建立一個數位時鐘。文件分為三個部分,每個部分描述如何使用開發板上不同功能來完成專案。第一部分採用開發板的 Wi-Fi,SDK 的 SNTP(簡單網路時間協定)程式庫,和 Grove 液晶顯示模組 (Grove LCD) 來顯示時間。第二部分展示了如何使用 Android 應用程式並採用 SDK 的智慧連接 (Smart connection) 功能來與 LinkIt 7687 通訊。第三部分展示了如何使用休眠模式來實現省電功能。


介紹

這是 LinkIt 7687 數位時鐘教學文件的第一部。本文件將教您:

  • 如何將數位時鐘使用的硬體組合起來
  • 使用LinkIt SDK v3 的 SNTP 庫來訪問 NTP (Network Time Protocol) 伺服器以獲得時間資訊
  • 使用由 Seeed GitHub 移植的 LCD 庫 (lcd.c 與 lcd.h) 來控制 Grove LCD 模組

前置作業

您必須先準備好以下作業:

開始之前

如果您未使用過 LinkIt 7687 HDK 來建立專案, 必須先安裝 LinkIt SDK v3 並且設定您的開發環境。請先閱讀 LinkIt development platform for RTOS get started guide 來瞭解完整的資訊作為準備。

硬體設置

本章節說明建立專案所需要的硬體以及電子元件。

必備

為創建您專案的硬體,除了 LinkIt 7687 開發板之外,您還需要準備以下元件:

元件說明來源
LCDLCD RGB 模組Grove LCD backlight module
跳線 (jump wires)Seeed Studio
麵包板 (breadboard)基本型麵包板Seeed Studio
電阻兩個 4.7 k ohms 電阻Seeed Studio

註: 電阻的主要目的是為 I2C pull-up。 欲瞭解更多 I2C 請參照這裡

組合元件

本章節提供專案硬體設置的逐步指令。您可以使用以下圖當做參考。

第一步將 LinkIt 7687 開發板的 5V 接到麵包板上的紅色軌道
第二步將 GND 接到麵包板上的藍色軌道
第三步將 LCD 模組的 VCC 接到麵包板上的紅色軌道
第四步將 LCD 模組的 GND 接到麵包板上的藍色的軌道
第五步在麵包板上選一個垂直的軌道,將 LinkIt 7687 開發板的 SDA, LCD 模組的 SDA 以及 4.7k ohm 電阻的一邊都接到這個軌道。請參考下圖的白色接線
第六步將 4.7k ohm 電阻的另一端接到紅色的軌道 (5V)
第七步在麵包板上選另一個垂直的軌道,將 LinkIt 7687 開發板的 SCL, LCD 模組的 SCL 以及 4.7k ohm 電阻的一邊都接到這個軌道。請參考下圖的黃色跳線。
第八步將 4.7k ohm 電阻的另一端接到紅色的軌道 (5V)。

使用 Fritzing 示意元件組合起來的樣子如下:

註:該圖是使用 Fritzing 軟體完成。Grove LCD 模組的圖是從 johnny-five Github 取得。

軟體實作

本章節描述教程所需要用到的軟體,包括一個第三方的 API,和 C 原始碼的描述。此專案使用 LinkIt 7687 開發板的 Wi-Fi Station 模式,也使用 SNTP 程式庫來向 NTP 伺服器請求時間資訊,並使用 I2C 介面和 HAL I2C API 來與 Grove LCD 模組通訊。相關元件在以下軟體架構圖上以紅底白字標記

什麼是 SNTP?

Simple Network Time Protocol (SNTP) 是一個在 IP 網路上電腦系統之間的時鐘同步網路通訊協定。LinkIt 開發平臺 for RTOS 在中介軟體層(Middleware) 裡提供 SNTP 程式庫,讓應用程式可以很容易的取得時間資訊。

The HAL I2C API

此專案使用 I2C API 的低速寫入和 Grove LCD 模組通訊。請參考 HAL I2C API 來瞭解使用該 API 的完整細節。

The LCD library

此專案也使用了從 Seeed’s Grove_LCD_RGB_Backlight GitHub project 移植過來的 LCD API 來更新 Grove LCD 模組上的時間資訊顯示。

欲瞭解更多關於如何控制 LCD 模組請參考此專案

設定 main.c

為了讓開發板在 Wi-Fi Station 模式下工作,你需要修改 main.c 檔來設 AP (無線網路基地台) 參數。包括以下:

  • AP_SSID: 您的 AP SSID
  • AP_PWD: 您的 AP 密碼
  • NTP_SERVER1: 您的區域 NTP server1
  • NTP_SERVER2: 您的區域 NTP server2
  • TIMEZONE_OFFSET: 您的時區偏移,例如,臺灣地區(UTC+ 8),格林威治(UTC+ 0)
  • CURR_CENTURY: 當前世紀, 目前為西元2000年

在 src 資料夾裡編輯 main.c 並且將以下資訊替換為對應您 AP 的資訊:

/* Modify the below configurations according to your environment */ 
#define AP_SSID             "your_SSID"  
#define AP_PWD              "your_pwd"
#define NTP_SERVER1         "time.stdtime.gov.tw"
#define NTP_SERVER2         "clock.stdtime.gov.tw"
#define TIMEZONE_OFFSET     8
#define CURR_CENTURY        2000
/* End of modification */

初始化 I2C 介面

呼叫 hal_gpio_init() 來初始化 GPIO 27 和 28, 然後再使用 hal_pinmux_set_function() 來設定它們的功能 (pinmux) 成 I2C1_CLK (SCL) 和 I2C1_DATA (SDA)。接下來,設定 LCD 模組所使用的 I2C 頻率為 100K。您可以使用以下代碼來完成此任務

static void lcd_init(void)
{
    hal_i2c_config_t i2c_init;
 
    hal_gpio_init(HAL_GPIO_27);
    hal_gpio_init(HAL_GPIO_28);
    hal_pinmux_set_function(HAL_GPIO_27,HAL_GPIO_27_I2C1_CLK);
    hal_pinmux_set_function(HAL_GPIO_28,HAL_GPIO_28_I2C1_DATA);
    hal_gpio_pull_up(HAL_GPIO_27);
    hal_gpio_pull_up(HAL_GPIO_28);
 
    i2c_init.frequency = HAL_I2C_FREQUENCY_100K;
    hal_i2c_master_init(HAL_I2C_MASTER_0, &i2c_init);
 
    /* ... */
}

定義 I2C callback 函數

為了使用 LCD 函式庫,您需要定義一個函數 _i2cWrite 並將其註冊。該函數實作使用一個簡單版的 HAL I2C API hal_i2c_master_send_polling 。請注意,此 API 函數將會同步等待到完成才返回。您可以使用以下代碼來完成此任務:

static void _i2c_write(uint8_t addr, uint8_t* data, uint8_t dataLen)
{
    hal_i2c_status_t ret;
 
    ret = hal_i2c_master_send_polling(HAL_I2C_MASTER_0, addr, data, dataLen);
    if(HAL_I2C_STATUS_OK != ret)
    {
        LOG_E(common, "_i2c_write(%X) FAIL: %d", addr, ret);
    }
}
 
static void lcd_init(void)
{
    /* ... */
 
    rgblcd_registerFunc(_i2c_write);
}

初始化 Wi-Fi

呼叫 wifi_init() 並輸入 SSID 和密碼參數, 再調用 dhcp_start() 來啟動 DHCP 服務。連線的結果將通過註冊的函數 ip_ready_callback() 通知。您可以使用以下代碼來完成此任務:

static void main_task(void *args)
{
    /* ... */
 
    LOG_I(common, "network init: %s, %s", AP_SSID, AP_PWD);
    network_init(AP_SSID, AP_PWD);
    LOG_I(common, "network init done");
 
    /* ... */
}
 
void network_init(const char* SSID, const char* password)
{
    /* ... */
 
    wifi_config.opmode = WIFI_MODE_STA_ONLY;
    strcpy((char *)wifi_config.sta_config.ssid, SSID);
    wifi_config.sta_config.ssid_length = strlen(SSID);
    strcpy((char *)wifi_config.sta_config.password, password);
    wifi_config.sta_config.password_length = strlen(password);
 
    wifi_init(&wifi_config, NULL);
    lwip_tcpip_init(&tcpip_config, WIFI_MODE_STA_ONLY);
 
    ip_ready = xSemaphoreCreateBinary();
 
    sta_if = netif_find_by_type(NETIF_TYPE_STA);
    netif_set_status_callback(sta_if, _ip_ready_callback);
    dhcp_start(sta_if);
 
    // wait for result
    xSemaphoreTake(ip_ready, portMAX_DELAY);
}
 
static void _ip_ready_callback(struct netif *netif)
{
    /* ... */
 
    LOG_I(common, "ip ready");
    xSemaphoreGive(ip_ready);
}

使用 SNTP 函式庫

通過定義 sntp_setservername() 來設置 NTP 伺服器,並呼叫函數 sntp_init() 來使用 SNTP 庫。接收到的時間資訊將被自動儲存到 HAL RTC API 裡,並在下一章節中被取出來使用。您可以使用以下代碼來完成此任務:

static void main_task(void *args)
{
    /* ... after network inited ...*/
 
    //SNTP start.
    sntp_setservername(0, NTP_SERVER1);
    sntp_setservername(1, NTP_SERVER2);
    sntp_init();
    LOG_I(common, "SNTP inited");
 
    /* ... */
}

創建一個任務來更新時間資訊

創建一個迴圈函數(_sntp_check_loop)以每秒一次的速度不斷的更新 LCD上的時間顯示。此迴圈實作會呼叫 HAL RTC API hal_rtc_get_time(),再利用 timezone_shift 來調整時區,最後使用 LCD 函式庫(在 lcd.h)來控制 Grove LCD 模組來更新資訊。

該 LCD 庫是由 Seeed's github project 所移植的。 移植過程包含以下工作:

  • 從 C++ 語言轉換為 C 語言
  • 使用 HAL I2C polling 模式 API 來取代 Wire I2C API

欲瞭解更多關於如何使用該函式庫的細節,請參考 Seeed’s Grove_LCD_RGB_Backlight GitHub project

以下代碼描述此迴圈的內容:

static void _sntp_check_loop(void)
{
    hal_rtc_time_t r_time;
    hal_rtc_status_t ret;
 
    while(1) {
        ret = hal_rtc_get_time(&r_time);
        if (ret == 0)
        {
            char buf[20];
 
            _timezone_shift(&r_time, TIMEZONE_OFFSET);
            LOG_I(common, "%04d/%d/%d %02d:%02d:%02d", 
                  r_time.rtc_year+CURR_CENTURY, r_time.rtc_mon, r_time.rtc_day, 
                  r_time.rtc_hour, r_time.rtc_min, r_time.rtc_sec);
 
            rgblcd_clear();
            snprintf(buf, 19, "%04d/%d/%d", r_time.rtc_year+CURR_CENTURY, r_time.rtc_mon, r_time.rtc_day);
            rgblcd_setCursor(0, 0);
            rgblcd_write_str(buf);
            snprintf(buf, 19, "%02d:%02d:%02d", r_time.rtc_hour, r_time.rtc_min, r_time.rtc_sec);
            rgblcd_setCursor(0, 1);
            rgblcd_write_str(buf);
        }
 
        // wait 1 sec and retry
        vTaskDelay(MS2TICK(1000));
    }
}
 
static void main_task(void *args)
{
    /* ... after SNTP inited ... */
 
    vTaskDelay(MS2TICK(1000));
    _sntp_check_loop();
}

執行您的應用

你可以在這裡下載完整的原始程式碼。

按照 LinkIt 7687 HDK 入門 的指令來上傳和執行代碼。完成後, 您應該就能在 LCD 顯示器裡看見當前時間資訊。

結論

在本教程中,你建立了一個應用程式, 使用 LinkIt 7687 開發板的 Wi-Fi 功能和 SNTP 函式庫來訪問 NTP 伺服器及取得時間資訊,並通過 I2C 介面在 LCD 模組上顯示它。在第二部中,您將學習如何使用智慧連接功能,來創建這個數位時鐘。關於聯發科技 LinkIt 7687 開發板的更多資訊,請參閱 LinkIt 開發平臺 for RTOS 開發者指南