SPI協(xié)議和OLED詳解及裸機程序開發(fā)分析

版權(quán)聲明:本文為小斑馬學(xué)習(xí)總結(jié)文章,技術(shù)來源于韋東山著作砌们,轉(zhuǎn)載請注明出處杆麸!

一、SPI協(xié)議介紹

市面上的開發(fā)板很少接有SPI設(shè)備浪感,但是SPI協(xié)議在工作中經(jīng)常用到昔头。開發(fā)SPI模塊,上面有SPI Flash和SPI OLED影兽。OLED就是一塊顯示器揭斧。
裸板程序會涉及兩部分:

  • 用GPIO模擬SPI
  • 用S3C2440的SPI控制器

介紹下SPI協(xié)議,硬件框架如下:
  • SCK:提供時鐘
  • DO:作為數(shù)據(jù)輸出
  • DI:作為數(shù)據(jù)輸入
  • CS0/CS1:作為片選

同一時刻只能有一個SPI設(shè)備處于工作狀態(tài)。

設(shè)現(xiàn)在2440傳輸一個0x56數(shù)據(jù)給SPI Flash讹开,時序如下:
首先CS0先拉低選中SPI Flash盅视,0x56的二進(jìn)制就是0b0101 0110,因此在每個SCK時鐘周期旦万,DO輸出對應(yīng)的電平闹击。

SPI Flash會在每個時鐘周期的上升沿讀取D0上的電平。
在SPI協(xié)議中成艘,有兩個值來確定SPI的模式赏半。
CPOL:表示SPICLK的初始電平,0為電平淆两,1為高電平
CPHA:表示相位断箫,即第一個還是第二個時鐘沿采樣數(shù)據(jù),0為第一個時鐘沿秋冰,1為第二個時鐘沿

CPOL CPHA 模式 含義
0 0 0 初始電平為低電平瑰枫,在第一個時鐘沿采樣數(shù)據(jù)
0 1 1 初始電平為低電平,在第二個時鐘沿采樣數(shù)據(jù)
1 0 2 初始電平為高電平丹莲,在第一個時鐘沿采樣數(shù)據(jù)
1 1 3 初始電平為高電平光坝,在第二個時鐘沿采樣數(shù)據(jù)

常用的是模式0和模式3,因為它們都是在上升沿采樣數(shù)據(jù)甥材,不用去在乎時鐘的初始電平是什么盯另,只要在上升沿采集數(shù)據(jù)就行。
極性選什么洲赵?格式選什么鸳惯?通常去參考外接的模塊的芯片手冊。比如對于OLED叠萍,查看它的芯片手冊時序部分:

SCLK的初始電平我們并不需要關(guān)心芝发,只要保證在上升沿采樣數(shù)據(jù)就行。

二苛谷、使用GPIO實現(xiàn)SPI協(xié)議操作OLED

現(xiàn)在開始寫代碼辅鲸,使用GPIO實現(xiàn)SPI協(xié)議操作。
現(xiàn)在想要操作OLED腹殿,通過三條線(SCK独悴、DO、CS)與OLED相連锣尉,這里沒有DI是因為2440只會向OLED傳數(shù)據(jù)而不用接收數(shù)據(jù)刻炒。
要用GPIO來實現(xiàn)SOC向OLED寫數(shù)據(jù),這一層用gpio_spi.c來實現(xiàn)自沧,負(fù)責(zé)發(fā)送數(shù)據(jù)坟奥。
對于OLED,有專門的指令和數(shù)據(jù)格式,要傳輸?shù)臄?shù)據(jù)內(nèi)容爱谁,在oled.c這一層來實現(xiàn)晒喷,負(fù)責(zé)組織數(shù)據(jù)。

因此管行,我們需要實現(xiàn)以上兩個文件厨埋。
需要實現(xiàn)的函數(shù):先SPI初始化SPIInt()邪媳,再初始化OLEDOLEDInit()捐顷,最后再顯示OLEDPrint()。
新建一個gpio_spi.c文件雨效,實現(xiàn)SPI初始化SPIInt()
void SPIInit(void)
{
   /* 初始化引腳 */
   SPI_GPIO_Init();
}

再具體實現(xiàn)SPI_GPIO_Init()迅涮。這里使用GPIO實現(xiàn)SPI協(xié)議,電路圖如下:
GPF1作為OLED片選引腳徽龟,設(shè)置為輸出叮姑;
GPG2作為FLASH片選引腳,設(shè)置為輸出据悔;
GPG4作為OLED的數(shù)據(jù)(Data)/命令(Command)選擇引腳传透,設(shè)置為輸出;
GPG5作為SPI的MISO极颓,設(shè)置為輸入朱盐;
GPG6作為SPI的MOSI,設(shè)置為輸出菠隆;
GPG7作為SPI的時鐘CLK兵琳,設(shè)置為輸出;

/* 用GPIO模擬SPI */
static void SPI_GPIO_Init(void)   {
/* GPF1 OLED_CSn output */
GPFCON &= ~(3<<(1*2));
GPFCON |= (1<<(1*2));
GPFDAT |= (1<<1);

/* GPG2 FLASH_CSn output
* GPG4 OLED_DC   output
* GPG5 SPIMISO   input
* GPG6 SPIMOSI   output
* GPG7 SPICLK    output
*/
GPGCON &= ~((3<<(2*2)) | (3<<(4*2)) | (3<<(5*2)) | (3<<(6*2)) | (3<<(7*2)));
GPGCON |= ((1<<(2*2)) | (1<<(4*2)) | (1<<(6*2)) | (1<<(7*2)));
GPGDAT |= (1<<2);
}

再新建一個oled.c文件骇径,以實現(xiàn)初始化OLEDOLEDInit()

void OLEDInit(void)
{
   /* 向OLED發(fā)命令以初始化 */
}

查閱OLED數(shù)據(jù)手冊SPEC UG-2864TMBEG01.pdf可以得知其初始化流程和參考的初始化代碼:

void OLEDInit(void)
{
/* 向OLED發(fā)命令以初始化 */
OLEDWriteCmd(0xAE); /*display off*/ 
OLEDWriteCmd(0x00); /*set lower column address*/ 
OLEDWriteCmd(0x10); /*set higher column address*/ 
OLEDWriteCmd(0x40); /*set display start line*/ 
OLEDWriteCmd(0xB0); /*set page address*/ 
OLEDWriteCmd(0x81); /*contract control*/ 
OLEDWriteCmd(0x66); /*128*/ 
OLEDWriteCmd(0xA1); /*set segment remap*/ 
OLEDWriteCmd(0xA6); /*normal / reverse*/ 
OLEDWriteCmd(0xA8); /*multiplex ratio*/ 
OLEDWriteCmd(0x3F); /*duty = 1/64*/ 
OLEDWriteCmd(0xC8); /*Com scan direction*/ 
OLEDWriteCmd(0xD3); /*set display offset*/ 
OLEDWriteCmd(0x00); 
OLEDWriteCmd(0xD5); /*set osc division*/ 
OLEDWriteCmd(0x80); 
OLEDWriteCmd(0xD9); /*set pre-charge period*/ 
OLEDWriteCmd(0x1f); 
OLEDWriteCmd(0xDA); /*set COM pins*/ 
OLEDWriteCmd(0x12); 
OLEDWriteCmd(0xdb); /*set vcomh*/ 
OLEDWriteCmd(0x30); 
OLEDWriteCmd(0x8d); /*set charge pump enable*/ 
OLEDWriteCmd(0x14); 
}

因此我們還要先實現(xiàn)OLEDWriteCmd()函數(shù)躯肌,對于OLED,除了SPI的片選破衔、時鐘清女、數(shù)據(jù)引腳,還有一個數(shù)據(jù)/命令切換引腳晰筛。

這里的D/C即數(shù)據(jù)(Data)/命令(Command)選擇引腳校仑,它為高電平時,OLED即認(rèn)為收到的是數(shù)據(jù)传惠;它為低電平時迄沫,OLED即認(rèn)為收到的是命令。
對于OLED卦方,命令由開啟/關(guān)閉顯示羊瘩、背光亮度等,具體有什么命令,可以查閱OLED的主控芯片手冊SSD1306-Revision 1.1 (Charge Pump).pdf尘吗,在9 COMMAND TABLE 有相關(guān)命令的介紹逝她。

因此,在編寫OLEDWriteCmd()時睬捶,需要先設(shè)置為命令模式

static void OLEDWriteCmd(unsigned char cmd)
{
   OLED_Set_DC(0); /* command */
   OLED_Set_CS(0); /* select OLED */

   SPISendByte(cmd);

   OLED_Set_CS(1); /* de-select OLED */
   OLED_Set_DC(1); /*  */
}

即:先設(shè)置為命令模式黔宛,再片選OLED,再傳輸命令擒贸,再恢復(fù)成原來的模式和取消片選臀晃。

片選函數(shù)和模式切換函數(shù)都比較簡單,設(shè)置為對應(yīng)的高低電平即可:

static void OLED_Set_DC(char val)
{
if (val)
    GPGDAT |= (1<<4);
else
    GPGDAT &= ~(1<<4);
}

static void OLED_Set_CS(char val)
{
if (val)
    GPFDAT |= (1<<1);
else
    GPFDAT &= ~(1<<1);
}

還剩下SPISendByte()函數(shù)介劫,它屬于SPI協(xié)議徽惋,放在gpio_spi.c里面:

void SPISendByte(unsigned char val)
{
int i;
for (i = 0; i < 8; i++)
{
    SPI_Set_CLK(0);
    SPI_Set_DO(val & 0x80);
    SPI_Set_CLK(1);
    val <<= 1;
}
}

發(fā)送數(shù)據(jù)要滿足SPI的時序要求,參考前面的介紹:

先設(shè)置CLK為低座韵,然后數(shù)據(jù)引腳輸出數(shù)據(jù)的最高位险绘,然后CLK為高,在CLK這個上升沿中,OLED就讀取了一位數(shù)據(jù)誉碴。接著左移一位宦棺,將原來的第7位移動到了第8位,重復(fù)8次黔帕,傳輸完成代咸。

再完成SPI_Set_CLK()和SPI_Set_DO():

static void SPI_Set_CLK(char val)
{
if (val)
    GPGDAT |= (1<<7);
else
    GPGDAT &= ~(1<<7);
}

static void SPI_Set_DO(char val)
{
if (val)
    GPGDAT |= (1<<6);
else
    GPGDAT &= ~(1<<6);
}

至此,SPI初始化和OLED初始化就基本完成了蹬屹,接下來就是OLED顯示部分侣背。
先了解一下OLED顯示的原理:
OLED長有128個像素,寬有64個像素慨默,每個像素用一位來表示贩耐,為1則亮,為0則滅厦取。
每一個字節(jié)數(shù)據(jù)Datax控制每列8個像素潮太,在顯存里面存放Data數(shù)據(jù)。
之后所需的操作就是把數(shù)據(jù)寫到顯存里面去虾攻,如何寫到顯存可以拆分成兩個問題:

  • ①怎么發(fā)地址
  • ②怎么發(fā)數(shù)據(jù)

OLED主控的手冊里介紹了三種地址模式铡买,我們常用的是頁地址模式(Page addressing mode (A[1:0]=10xb)),它把顯存的64行分為8頁霎箍,每頁對應(yīng)8行奇钞;選中某頁后,再選擇某列漂坏,然后就可以往里面寫數(shù)據(jù)了景埃,每寫一個數(shù)據(jù)媒至,地址就會加1,一直寫到最右端的位置谷徙,他會自動跳到最左端拒啰。
通過命令來實現(xiàn)發(fā)送頁地址和列地址,其中列地址分為兩次發(fā)送完慧,先發(fā)送低字節(jié)谋旦,再發(fā)送高字節(jié)。

假設(shè)每個字符數(shù)據(jù)大小為8x16屈尼,假如第一個字符位置為(page,col)册着,相鄰的右邊就是(page,col+8),寫滿一行跳至下一行的坐標(biāo)就是(page+2,col)鸿染。

/* page: 0-7
 * col : 0-127
 * 字符: 8x16象素
 */
void OLEDPrint(int page, int col, char *str)
{
int i = 0;
while (str[i])
{
    OLEDPutChar(page, col, str[i]);
    col += 8;
    if (col > 127)
    {
        col = 0;
        page += 2;
    }
    i++;
}
}

只要字符數(shù)組str[i]有數(shù)據(jù)指蚜,就調(diào)用OLEDPutChar(page, col, str[i])在指定位置顯示第一個字符乞巧,然后位置向右移動一個字符的大小涨椒,如果遇到行尾,再進(jìn)行換行绽媒,就這樣依次顯示完所有字符蚕冬。

現(xiàn)在開始實現(xiàn)最重要的OLEDPutChar()函數(shù)。把一個字符在OLED上顯示出來需要以下幾個步驟:

  • a. 得到字模
  • b. 發(fā)給OLED

字模我們可以從網(wǎng)上搜索相關(guān)資料獲取到是辕,將字模的數(shù)組oled_asc2_8x16[95][16]放在oledfont.c里面囤热,字符從空格開始,因此每次減去一個空格才是我們想要的字符获三。

如圖所示一個字符旁蔼,先以(page, col)為起點,顯示8位數(shù)據(jù)疙教,再換行棺聊,以(page+1, col)為起點顯示8位數(shù)據(jù)。
/* page: 0-7
 * col : 0-127
 * 字符: 8x16象素
*/
void OLEDPutChar(int page, int col, char c)
{
int i = 0;
/* 得到字模 */
const unsigned char *dots = oled_asc2_8x16[c - ' '];

/* 發(fā)給OLED */
OLEDSetPos(page, col);
/* 發(fā)出8字節(jié)數(shù)據(jù) */
for (i = 0; i < 8; i++)
    OLEDWriteDat(dots[i]);

OLEDSetPos(page+1, col);
/* 發(fā)出8字節(jié)數(shù)據(jù) */
for (i = 0; i < 8; i++)
    OLEDWriteDat(dots[i+8]);
}

顯示一個字符贞谓,就先獲取字模數(shù)據(jù)限佩,接著發(fā)出8字節(jié)數(shù)據(jù),再換行發(fā)出8字節(jié)數(shù)裸弦。

再來實現(xiàn)OLED設(shè)置坐標(biāo)位置函數(shù)祟同,先設(shè)置page:
D0~D2表示page數(shù)據(jù),D3-D7是固定的值理疙,因此每次寫的命令內(nèi)容為0xB0+page;

再設(shè)置列:
分兩次發(fā)送晕城,顯示發(fā)送低字節(jié)4位,再發(fā)送高字節(jié)四位窖贤;
static void OLEDSetPos(int page, int col)
{
OLEDWriteCmd(0xB0 + page); /* page address */

OLEDWriteCmd(col & 0xf);   /* Lower Column Start Address */
OLEDWriteCmd(0x10 + (col >> 4));   /* Lower Higher Start Address */
}

前面提到了OLED主控有三種地址模式砖顷,我們常用的是頁地址模式(Page addressing mode (A[1:0]=10xb))暇矫,雖然這是默認(rèn)的摸索,但還是設(shè)置一下比較好:

即先發(fā)送0x20择吊,再設(shè)置A[1:0]=10:

static void OLEDSetPageAddrMode(void)
{
   OLEDWriteCmd(0x20);
   OLEDWriteCmd(0x02);
}

在顯示中李根,一般都需一個清屏函數(shù)來清空當(dāng)前可能顯示的數(shù)據(jù)。清屏函數(shù)比較簡單几睛,往所有位置里面寫0即可:

static void OLEDClear(void)
{
int page, i;
for (page = 0; page < 8; page ++)
{
    OLEDSetPos(page, 0);
    for (i = 0; i < 128; i++)
        OLEDWriteDat(0);
}
}

再把地址模式OLEDSetPageAddrMode()和清屏函數(shù)OLEDClear()放在SPI_GPIO_Init()里房轿,在Makefile加上gpio_spi.o和oled.o。

最后在主函數(shù)里加上初始化和顯示函數(shù):

三所森、SPI_FLASH編程_讀ID

這節(jié)講解如何使用SPI操作Flash囱持,代碼上進(jìn)行修改,添加一個文件 spi_flash.c 和其頭文件 spi_flash.h 焕济。
先做一個最簡單的spi操作纷妆,讀取Flash的ID, SPIFlashID() 晴弃。
Flash的ID有廠家ID和設(shè)備ID掩幢,分別用pMID和pDID來保存。

根據(jù)Flash的芯片手冊 W25Q16DV.pdf 可以知道需要先發(fā)出一個指令0x90上鞠,再發(fā)送24位的地址0际邻,再讀取數(shù)據(jù)前8位是設(shè)備ID,然后是8位設(shè)備ID芍阎。進(jìn)行操作前必須要片選SPI Flash世曾,片選完還是釋放SPI Flash:
圖片來源白問網(wǎng)
void SPIFlashReadID(int *pMID, int *pDID)
{
SPIFlash_Set_CS(0); /* 選中SPI FLASH */

SPISendByte(0x90);

SPIFlashSendAddr(0);

*pMID = SPIRecvByte();
*pDID = SPIRecvByte();

SPIFlash_Set_CS(1);
}

把其中的發(fā)送24地址封裝成了一個函數(shù) SPIFlashSendAddr() :

static void SPIFlashSendAddr(unsigned int addr)
{
SPISendByte(addr >> 16);
SPISendByte(addr >> 8);
SPISendByte(addr & 0xff);
}

依次完成上面的子函數(shù),先是SPI片選谴咸,上一節(jié)的原理圖可以看到SPI Flash的片選是GPG2:

static void SPIFlash_Set_CS(char val)
{
if (val)
    GPGDAT |= (1<<2);
else
    GPGDAT &= ~(1<<2);
}

SPISendByte() 和前面OLED的是一樣的轮听,就不用寫了,因此就只剩下 SPIRecvByte() 岭佳,放在 gpio_spi.c 里面實現(xiàn):

unsigned char SPIRecvByte(void)
{
int i;
unsigned char val = 0;
for (i = 0; i < 8; i++)
{
    val <<= 1;
    SPI_Set_CLK(0);
    if (SPI_Get_DI())
        val |= 1;
    SPI_Set_CLK(1);
}
return val;    
}

在每個時鐘周期讀取DI引腳上的值血巍,對于SOC就是MISO引腳:

static char SPI_Get_DI(void)
{
if (GPGDAT & (1<<5))
    return 1;
else 
    return 0;
}

至此,讀取Flash的ID基本實現(xiàn)驼唱,最后在主函數(shù)里調(diào)用打印藻茂,分別在串口和OLED上顯示:

SPIFlashReadID(&mid, &pid);
printf("SPI Flash : MID = 0x%02x, PID = 0x%02x\n\r", mid, pid);

sprintf(str, "SPI : %02x, %02x", mid, pid);
OLEDPrint(4,0,str);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市玫恳,隨后出現(xiàn)的幾起案子辨赐,更是在濱河造成了極大的恐慌,老刑警劉巖京办,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掀序,死亡現(xiàn)場離奇詭異,居然都是意外死亡惭婿,警方通過查閱死者的電腦和手機不恭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門叶雹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人换吧,你說我怎么就攤上這事折晦。” “怎么了沾瓦?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵满着,是天一觀的道長。 經(jīng)常有香客問我贯莺,道長风喇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任缕探,我火速辦了婚禮魂莫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘爹耗。我一直安慰自己耙考,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布鲸沮。 她就那樣靜靜地躺著琳骡,像睡著了一般锅论。 火紅的嫁衣襯著肌膚如雪讼溺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天最易,我揣著相機與錄音怒坯,去河邊找鬼。 笑死藻懒,一個胖子當(dāng)著我的面吹牛剔猿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嬉荆,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼归敬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鄙早?” 一聲冷哼從身側(cè)響起汪茧,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎双吆,沒想到半個月后晰搀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恋谭,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年扩灯,在試婚紗的時候發(fā)現(xiàn)自己被綠了媚赖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡珠插,死狀恐怖惧磺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捻撑,我是刑警寧澤豺妓,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站布讹,受9級特大地震影響琳拭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜描验,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一白嘁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膘流,春花似錦絮缅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至彭谁,卻和暖如春吸奴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缠局。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工则奥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狭园。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓读处,卻偏偏與公主長得像,于是被迫代替她去往敵國和親唱矛。 傳聞我的和親對象是個殘疾皇子罚舱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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

  • 重點是說SPI通信協(xié)議,,,, 不要害怕協(xié)議因為協(xié)議是人規(guī)定的,,剛好我也是人......規(guī)定的協(xié)議既然能成為規(guī)范...
    楊奉武閱讀 1,003評論 0 1
  • 1、嵌入式系統(tǒng)的定義 (1)定義:以應(yīng)用為中心绎谦,以計算機技術(shù)為基礎(chǔ)管闷,軟硬件可裁剪,適應(yīng)應(yīng)用系統(tǒng)對功能燥滑、可靠性渐北、成本...
    榮卓然閱讀 1,820評論 0 5
  • 什么是嵌入式 IEEE(Institute of Electrical and Electronics Engin...
    Leon_Geo閱讀 3,707評論 1 20
  • 一切有為法,如夢幻泡影铭拧。凡是有變化的事物都是有規(guī)可尋赃蛛,有條件支撐恃锉。能使你失去判斷力的根源就是你的七情六欲,人非草木...
    暗夜迷林閱讀 222評論 0 0
  • 本書作者通過一系列社會考察呕臂,對印度這個古老的文明國度進(jìn)行反思與批判破托。在他的分析中,印度的問題就在其自身歧蒋,比如...
    27a5b41a0120閱讀 199評論 0 0