<前言>
到目前為止,我們測試過了7687 HDK和移植了LCD1602驅動,接下來我們要實際放到SNTP範例中,將獲取的SNTP實時時間,顯示在LCD1602上面,本篇將是完整的實作,並帶過何謂SNTP和其標頭格式,我們只要專注在上層的FreeRTOS框架下各Task的交互
<準備>
1. 7687 HDK開發板
2. LCD1602-I2C(PCF8574)
3. MicroUSB傳輸線
<實作>
1. 首先簡介何謂SNTP,SNTP為Simple Network time Protocol,主要用途用在時間的同步,顧名思義,是NTP的簡化版本,較輕量,但精準度稍低一些但足夠一般應用上使用,以下是一些SNTP規則和標頭:
SNTP繼承了NTP的timestamp format(在RFC-1305有詳細介紹),NTP data 主要用整數或是固定位數來表示,
a. 使用bit-endian
b. 由0從左邊位元開始
c. 皆為正數 d. 所有的位元皆可以用來表示數值,假設第一位元也被使用時,預設會有一個preceding bit 0
Timestamp 表示方式
a. 64 bits unsigned fixed-point number
b. 從西元1900年1月1日開始計算秒數
c. 64 bits中的 前32 bits表示整數
d. 64 bits中的 後32 bits表示小數,要是在小數點後數值不到32bits會主動設為0
該timestamp format可以表示的位元數,從4,294,967,295秒(2^32)再加上200 picosecond(1/10^12s)的精確度。
NTP message format的封包表示方式如下
+---------------------------------------+
| IP|UDP|NTP message |
+---------------------------------------+
使用的UDP port number為 123
下表是NTP message header fields
各欄位就不詳述了,可參考詳細說明https://tools.ietf.org/html/rfc2030
2. 首先規劃共以下4個task
- 1. user_entry<=>主task
- 2. verify_proc<=>驗證sntp的task,這是範例原有的task
- 3. pwm_task<=>用來啟用和判斷,蜂鳴器報時的task
- 4. wifi,dhcp等等其他task<=>不涉及使用者層
复制代码
3. 因為必須等待verify_proc完成驗證後,報時比較的時間才為有效的,因此我們額外使用一個Semaphore,用來保證兩個Task的先後關係,在main.c中加入以下函式
- static SemaphoreHandle_t sntp_sem=NULL;
- static void sntp_sem_new( void )
- {
- if (sntp_sem == NULL) {
- sntp_sem = xSemaphoreCreateBinary();
- }
- if( sntp_sem == NULL ){
- LOG_E(sntp_client_main, "Error creating semaphore!");
- while(1);
- }
- }
- static void sntp_sem_lock( void )
- {
- while(xSemaphoreTake( sntp_sem, portMAX_DELAY ) != pdPASS );
- }
- static void sntp_sem_unlock( void )
- {
- xSemaphoreGive(sntp_sem);
- }
复制代码
這裡使用SemaphoreBinary,假設小伙伴們已經熟悉Semaphore的原理
4. 因為SNTP範例的sntp_init函式會設定RTC時間,而我們也要將RTC時間取出,用來給LCD1602來顯示,因此參考GCC版本的範例,我們增加以下函式和變數宣告
- static hal_rtc_time_t r_time;
- static void _timezone_shift(hal_rtc_time_t* t, int offset_hour)
- {
- struct tm gt;
- struct tm *nt;
- time_t secs;
- gt.tm_year = t->rtc_year + (CURR_CENTURY-1900);
- gt.tm_mon = t->rtc_mon-1;
- gt.tm_mday = t->rtc_day;
- gt.tm_wday = t->rtc_week;
- gt.tm_hour = t->rtc_hour;
- gt.tm_min = t->rtc_min;
- gt.tm_sec = t->rtc_sec;
- secs = mktime(>);
- secs += offset_hour * 3600;
- nt = gmtime(&secs);
- if (!nt) {
- nt = localtime(&secs);
- srand(secs);
- }
- t->rtc_year = (nt->tm_year % 100);
- t->rtc_mon = nt->tm_mon + 1;
- t->rtc_day = nt->tm_mday;
- t->rtc_week = nt->tm_wday;
- t->rtc_hour = nt->tm_hour;
- t->rtc_min = nt->tm_min;
- t->rtc_sec = nt->tm_sec;
- }
复制代码
以上除了取出RTC時間以外還將時區作位移,依照經度計算
5. 再來增加蜂鳴器報時的Task,pwm_task函式
- TaskHandle_t xPwm = NULL;
- static void pwm_task(void *args)
- {
- uint32_t total_count = 0;
- uint32_t duty_cycle = 0;
- uint32_t tone[]={262,294,330,349,392,440,494,523,587,659,698,784,880};
-
- hal_gpio_init(HAL_GPIO_4);
- /* Call hal_pinmux_set_function to set GPIO pinmux, if EPT tool was not used to configure
- the related pinmux */
- hal_pinmux_set_function(HAL_GPIO_4, HAL_GPIO_4_PWM2);
- /*Set source clock to 32KHz*/
- hal_pwm_init(HAL_PWM_CLOCK_2MHZ);
- LOG_I(sntp_client_main, "Starting alARM...");
-
- while(1)
- {
- vTaskSuspend(NULL);
-
- LOG_I(sntp_client_main, "Alarm clock checking...");
- if(r_time.rtc_sec >= ALARM_SEC && r_time.rtc_sec < ALARM_SEC+3)
- {
- /*Set frequency to Random Hz*/
- hal_pwm_set_frequency(HAL_PWM_2, tone[rand()%13], &total_count);
- /*Calculate the duty cycle when duty ratio is 50%*/
- duty_cycle = (total_count * 50) / 100;
- hal_pwm_set_duty_cycle(HAL_PWM_2, duty_cycle);
-
- hal_pwm_start(HAL_PWM_2);
- }
- else
- {
- hal_pwm_stop(HAL_PWM_2);
- }
- }
- }
复制代码
其中程式碼tone array為各個音階,用來產生隨機的頻率聲音,PWM則是設置了內部的晶振(2M Hz),腳位我設定為J36上的GPIO04,之後依照頻率計算duty_cycle值,用來產生符合佔波比50%特定頻率的PWM輸出(Channel 2),報時時間由ALARM_SEC和ALARM_MIN決定,這裡我用成每分鐘的第10秒報時,rand()%13用來隨機產生音頻頻率
6. 因為LCD1602要循環顯示時間,所以GCC範例版本的顯示函式要作修改,在此之前需要等待verify_proc完成SNTP的驗證工作,所以我們先改好verify_proc,將Semaphore拿取,等待驗證完畢後釋放
- static void verify_proc(void *args)
- {
-
- hal_rtc_time_t r_time;
- hal_rtc_status_t ret = HAL_RTC_STATUS_OK;
- sntp_sem_lock();
- LOG_I(sntp_client_main, "Running process...");
- for (int i = 0 ; i < 90; i++) {
- LOG_I(sntp_client_main, "Waiting for SNTP success [%d]", i);
- ret = hal_rtc_get_time(&r_time);
- if (ret == HAL_RTC_STATUS_OK && (r_time.rtc_year != 0 || r_time.rtc_mon != 1 || r_time.rtc_day != 1)) {
- LOG_I(sntp_client_main, "SNTP success [%d]", i);
- LOG_I(sntp_client_main, "cur_time[%d,%d,%d,%d]", r_time.rtc_year, r_time.rtc_mon, r_time.rtc_day, r_time.rtc_week);
- LOG_I(sntp_client_main, "[%d]cur_time[%d:%d:%d]", ret, r_time.rtc_hour, r_time.rtc_min, r_time.rtc_sec);
- sntp_stop();
- break;
- }
- vTaskDelay(1000 / portTICK_RATE_MS);
- }
- rgblcd_write_str("success");
- LOG_I(sntp_client_main, "test_proc TaskDelete");
- LOG_I(sntp_client_main, "example project test success.");
- sntp_sem_unlock();
- vTaskDelete(NULL);
- }
复制代码
這樣的好處可以確保報時task會在之後才啟動
7. 顯示循環函式_sntp_check_loop首先會先等待verify_proc釋放Semaphore,之後才會進行顯示和報時判斷
- static void _sntp_check_loop(void)
- {
- hal_rtc_status_t ret;
-
- sntp_sem_lock();
- LOG_I(sntp_client_main, "Running timer...");
- //Create a alarm clock task
- portBASE_TYPE type = xTaskCreate(pwm_task, "pwm_alarm", 1024 / sizeof(portSTACK_TYPE), NULL, TASK_PRIORITY_NORMAL, &xPwm);
- LOG_I(sntp_client_main, "xTaskCreate alarm_proc -- %d", type);
- sntp_sem_unlock();
- while(1)
- {
- ret = hal_rtc_get_time(&r_time);
- if (ret == 0)
- {
- char buf[20];
-
- vTaskResume(xPwm);
-
- _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));
- }
-
- }
复制代码
其中運用到了vTaskSuspend和vTaskResume機制,確保一時間內只有一個While會執行,不會卡在任何一個While loop上,即每一秒判斷一次時間後就停止,丟回給主Task顯示循環函式繼續執行While loop,等待下一秒循環時再次執行報時判斷task
8. 最後我們必須先在main中初始化Semaphore,並設定他的狀態
- sntp_sem_new();
- sntp_sem_unlock();
复制代码
確保要先unlock,才不會後面task無法lock
9. 在sntp_client函式的最後增加呼叫_sntp_check_loop顯示循環函式
10. 修改user_entry函式,讓他可以進行LCD的初始化
- static void user_entry(void *args)
- {
- LOG_I(common, "LCD init");
- lcd_init();
- rgblcd_begin(16, 2);
- rgblcd_backlight();
- LOG_I(common, "LCD init done");
-
- rgblcd_setCursor(0, 0);
- rgblcd_write_str("Connecting AP ");
-
- lwip_net_ready();
-
- rgblcd_write_str("OK");
- rgblcd_setCursor(0, 1);
- rgblcd_write_str("SNTP.....");
- sntp_client();
- }
复制代码
其中SNTP的執行成功後會額外顯示success,可在verify_proc函式看到,以上就完成了設計
11. 若要修改報時時間可以參考以下判斷方式
- r_time.rtc_min == ALARM_MIN && r_time.rtc_sec >= ALARM_SEC && r_time.rtc_sec < ALARM_SEC+3
复制代码
其中+3是表示響三秒
12. 修改後儲存,進行編譯
Fig.3 確保沒警告和錯誤
13. 拿掉J25跳帽重新進行燒寫,您就可以看到每分鐘10秒時就會鳴叫三秒的SNTP報時時鐘了,圖稍後補上或者在結案篇上面張貼,有興趣的可以跟我索取完整的專案工程
小結:
本篇運用FreeRTOS的Task控制和Task溝通機制(Semaphore),並整合一個產生PWM的Task用來控制蜂鳴器頻率和鳴叫時間,加上原來的顯示功能,是有趣的SNTP實時報時時鐘,供小伙伴們參考學習
0
评分
-
查看全部评分
|
|
|
|