2014年10月25日 星期六

LCD I2C模組使用.

http://www.geeetech.com/wiki/index.php/Serial_I2C_1602_16%C3%972_Character_LCD_Module
1602I2C 1.jpg 1602I2C 2.jpg
This is another great blue/yellow backlight LCD display. As the pin resources of Arduino controller is limited, your project may be not able to use normal LCD shield after connected with a certain quantity of sensors or SD card. However, with this I2C interface LCD module, you will be able to realize data display via only 2 wires. If you already has I2C devices in your project, this LCD module actually cost no more resources at all. It is fantastic for Arduino based project.

  • Interface: I2C
  • I2C Address: 0x27
  • Pin Definition : VCC、GND、SDA、SCL
  • Back lit (Green with white char color)
  • Supply voltage: 5V
  • Size : 27.7mm×42.6mm
  • Contrast Adjust : Through Potentiometer
  • Only employ two I/O interface
Here is pic shows how to connect an Arduino 1602 I2C module.The following is a table describing which pins on the Arduino should be connected to 1602 I2C LCD module.
1602I2C 4.jpg
1602I2C table.jpg
1602I2C 3.jpg
Spinnig the potentimeter clockwise is to increase contrast ,spinning unclockwise is to decrease it

Arduino 1602 I2C library only Arduino IDE 023!
Arduino 1602 I2C library for Arduino IDE 1.0   http://www.geeetech.com/Documents/LiquidCrystal_I2Cv1-1.rar
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
void setup()
{
 lcd.init();                      // initialize the lcd 
 // Print a message to the LCD.
 lcd.backlight();
 lcd.print("Hello, world!");
}
void loop()
{
}


=========================================================
來源: 




由於最後我們的作品希望可以展示時間和各種需要顯示的狀態,因此決定安裝一塊LCD模組,不過剛剛有提到過,我們有許多各種需要顯示的東西,因此表示我們的Arduino UNO已經沒有辦法再提供豐富的腳位給我們的LCD使用。



在Cooper Maa大大中的一篇文中"Arduino 筆記 – Lab9 在 2x16 LCD 上顯示 "Hello World" 訊息",我們可以知道即使使用了最少IO port的做法,也得要有四條data線、RS暫存器選擇線和E致能線,加上電源兩條,總共就得消耗八條線路,其中六條還是用去了digital,以UNO來說,幾乎用去了大半,這樣一來我可能得再購買一塊arduino分別控制LCD和其他裝置,這是相當不方便的,不僅要花錢還得另外進行兩塊UNO的連線,否則就是要購買mega來使用。

(內心:...........但是我沒這麼多錢,而且我懶.............我就是只有一塊UNO,究竟有沒有辦法不要佔去這麼多digital腳位,又可以讓我使用需要很多腳位的LCD呢?)



碰巧被我找到這麼一塊好東西,這樣一來我連一根digital腳位都沒有消耗,搭配RTC模組使用,甚至可以說,我一根腳位都沒有浪費的使用,那就是I2C模組拉QWQ!!!!!(QWQ,這是一種網路表情符號,表示很感動的意思。)


本篇最一開始就是LCD I2C模組的圖片
http://www.geeetech.com/wiki/index.php/Serial_I2C_1602_16%C3%972_Character_LCD_Module


不過外觀看起來和一般的LCD沒什麼太多的不同,唯一有不同的就是多插了一塊小IC晶片PCF8574,他是I2C的8bits擴充IC,利用他就可以將Arduino的腳位擴充,或許將來的專題我可以使用它來做許多多腳位的裝置。

PCF8574的data sheet
http://www.datasheetcatalog.org/datasheet2/b/0fjjhr6h643gldhx3o1rgkyk7ucy.pdf

關於PCF8574有個專業的教學網站在說明這塊IC怎麼使用
http://garagelab.com/profiles/blogs/tutorial-arduino-i-o-port-expander-with-pcf8574
他還提到怎麼應用在一般的LCD上面,使一般的LCD也變成I2C的LCD模組
http://garagelab.com/profiles/blogs/tutorial-lcd-using-only-2-arduino-pins-with-pcf8574-and-i2c


接下來這邊來說說實作方式


在剛剛的網站裡面有下載檔案,我將下載網址連過來。
http://www.geeetech.com/wiki/index.php/Serial_I2C_1602_16%C3%972_Character_LCD_Module

總共分成0023和1.0版本可以使用
Arduino IDE 023
Arduino IDE 1.0


開啟之後可以稍微看看範例檔案,下列的程式我有稍微修改過。

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
// 0x27是LCD模組的地址,16表示有十六欄位,2表示總共有2欄
LiquidCrystal_I2C lcd(0x27,16,2);  
void setup()
{
    //LCD初始化
    lcd.init();   
    lcd.init();
    //開啟背光
    lcd.backlight();
    //清除先前畫面
    lcd.clear();
    //設定第一個字顯示座標
    lcd.setCursor(0,0);
    //顯示字串
    lcd.print("Hello, world!");
}
void loop()
{
}

在導入.h檔和定義好LCD I2C之後,在setup那邊需要初始化LCD和開啟背板光源,之後只要設定座標和使用lcd.print即可顯示我們想要顯示的資料,用起來實在是相當的方便,以後只要Serial.print可以顯示,LCD上面一樣可以顯示。

舉例來說,我將Arduino的第一個基礎範例analogReadSerial來進行修改。

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);
void setup() {
  Serial.begin(9600);
  lcd.init();   // initialize the lcd 
  lcd.init();
  // Print a message to the LCD.
  lcd.backlight();
  lcd.clear();
}

void loop() {
  int sensorValue = analogRead(A0);
  lcd.setCursor(0,0);
  lcd.println(sensorValue, DEC);
}

我將該設定的部分設定好之後,把Serial.println換成lcd.print即可顯示。

這東西實在是太方便了~~~~

不過雖然Arduino的模組們用起來很方便,但是等到會用了之後,還是建議大家回到最初使用LCD的方式玩看看,才能夠真正的了解為什麼可以用兩隻腳就可以操控LCD是一件多麼讓人感動的事情,而且也可以同時了解四位元和八位元的意義是什麼,這樣回過頭來去尋找源頭的過程中,就像是考古學家一樣,會發現許多前人留下的有趣故事唷。

2014年10月19日 星期日

单片机数据通信之单总线数据传输分析

http://www.ednchina.com/ART_8800519406_29_35572_TA_d82944e3.HTM?click_from=8800024069,9950021346,2014-10-18,EDNCOL,NEWSLETTER


单片机数据通信之单总线数据传输分析

作者: / 上网日期: 2014年10月16日  评论[ 0 ]  
分享到:  新浪微博   qq空间   qq微博   人人网   百度搜藏  
  字号: 
关键字:单片机   控制器   数据通信  
单片机干不了大事,必须得配上各种外设,那么了解单片机与传感器之间的数据通信就显得必不可少了。常见的单片机数据通信方式有SPI,IIC,RS232,单总线等等。每种通信方式都有相应的时序图,分析时序图并完成代码的编写是单片机学习者的必修课。本文以DS18B20为例分析一下单总线数据传输。
DS18B20是单总线数据传输,因此对于时序的要求就非常的高,学会分析其时序图是非常有必要的。
例说单片机数据通信之单总线数据传输
1.初始化时序图分析:
例说单片机数据通信之单总线数据传输
首先是由总线控制器拉低总线,维持480us。在480us后释放总线,由上拉电阻讲总线拉高。等待5-60us后,DS18B20开始响应,会将数据总线拉低60-240us.之后便释放总线,由上拉电阻拉高总线。转换为代码如下:
u8 dsbInit() //初始化,返回0表示DS18B20无反应,反之有响应
{
dsbDQStat(0); //控制器拉低总线
delay500us(); //拉低总线一段时间
dsbDQStat(1); //释放总线
delay60us(); //等待DS18B20响应
if(dsb_DQ) //如果没有相应直接返回0
{
return 0;
}
delay240us(); //有响应则等待响应结束
return 1; //返回初始化状态
}
2.读时序图分析:
例说单片机数据通信之单总线数据传输
首先由控制器将总线拉低>1us的时间,此时控制器释放总线,如果此时控制器采样为低电平,那么读到的值便是0,如果为高电平,则读到的值为1。注意图中标有一个15us,其意思便是控制器采样在15us内完成。15us后是由上拉电阻将总线拉高维持45us。整个读周期为15+45=60us。这个周期的时间也是得控制的。转换为代码如下:
u8 dsbReadByte() //读出一个字节的数据,从低位开始读取
{
u8 i,tmp = 0;
for(i = 0;i < 8;i++)
{
dsbDQStat(0); //控制器拉低总线
tmp >>= 1; //低位开始读
dsbDQStat(1); //释放总线
if(dsb_DQ) tmp |= 0x80;
delay15us();
delay45us(); //控制周期时间
}
return tmp;
}
3.写时序图分析:
例说单片机数据通信之单总线数据传输
首先由控制器拉低总线15us,之后,如果要写入0,则继续拉低总线并为此45us.如果要写入1则释放总线由上拉电阻拉高总线,也为此45us。写时序相对比较简单,转换为代码如下:
void dsbWriteByte(u8 dat)//写一个字节的数据,从低位开始
{
u8 i;
for(i = 0;i < 8;i++)
{
dsbDQStat(0); //控制器拉低总线
delay15us(); //维持15us
if(dat & 0x01) dsbDQStat(1);
else dsbDQStat(0);
dat >>= 1;
delay45us();
dsbDQStat(1); //45us后释放总线
}
}
DS18B20的三个时序图就分析完了,DS18B20只是单总线数据通信中的一个例子,大家了解了DS18B20时序图的分析,那么就可以试试分析DHT11的时序图完成其初始化函数,以及读数据函数。

淺談 PLL - 鎖相環(PLL: Phase-locked loops)

淺談 PLL - 鎖相環(PLL: Phase-locked loops)
以下為小弟個人的理解方式來討論:
很多人還依然對 PWM 或是 OPA 或 BJT 依然茫然,
是因為在純軟體設計上很少會接觸到實際電路的問題,
小弟本身對許多軟體也是不了解的多...
而相鎖部份小弟希望用軟體的角度去說明這個技術,
由維基找到的資料我們可以在圖中看到幾個區塊:
鑑頻鑑相器
低通濾波器
壓控振盪器
反饋迴路
而透過程式中的部份呢 P 相位 L 鎖定 L 迴圈 <= 第三個最簡單我們先用程式寫下
while (1) <= L 迴圈完成 
而相位鎖定部份先解說與舉例其原理和用途,
PLL 在實際上電路產品的運用範圍很廣範,比如電視的 TUNER
選台器,而選台器是一種什麼樣的設計和原理?!
如果你已經具備了如超外差接收器的觀念,對於 IF 中頻、本振等等部份已經了解,那麼... 很棒,但是我會用更簡單的方式說明,
一個影像是由 6MB 的頻寬所組成:
這裡面包括了 3.58 MHZ 的色載波訊號,也被稱為繫色訊號,
以及 1 MHZ 的音頻訊號,調變訊號,
而傳輸這 6MB 的訊號是由 數十MB到上GHZ 的載波訊號來傳輸,而我們所說的 TUNER 正是解這個載波訊號部份,而取的 6MB 我們要的影音資料,
因此 PLL 也就是用在這部份,TUNER 的原理是產生出一個本振頻然後去跟載波混合,然後會產生出一個 IF 中頻,然後做中頻放在再解出訊號資料,過去中放是很難做的一種技術,而載波頻率太高並且訊號非常微弱,可能是 -90DB 以下,所以把訊號降到中頻率後再放大到幾十萬倍,就可以進行處理了,所以 TUNER 的主要功能是把一本地振盪訊號,和外來訊號加到混波器中,會出現 A+B 和 A-B 及 A 與 B 四種訊號,IF 中頻訊號即是 A-B 訊號,
而這裡我們會用到相鎖環,當我們產生出的本振訊號過高或是過低於某一頻率的載波時,TUNER 裡面的鎖定訊號就會沒抓到,那我們就進行微調將頻率上一點或是下一點,所以 LOCK 鎖定部份我們需要一個判斷式來幫忙,IF( detect(LOCK) ) <= 這部份就產生了,所以我們把式子加到一起去變成:
while(1)
{
if ( detect(LOCK) )
}
但是我們不知道要往上調是鎖住還是往下調才能鎖住?!
假定我們先將一個數值範圍給設定住,比如頻率部份只能夠用2個BYTE去控制,第一個BYTE是粗調整後一個BYTE是細調整,而我們所要找到的載波是 55.25 MHZ 第 1 臺的位置,那麼可能我們在粗調整部份已經找到是 10 的值給進去,細調整部分我們則給 255 或是 0,然後一直往上調上去,一直到鎖定訊號後完成,再往上追 15 個或是 20 個值再往下調整到鎖定,把下範圍與上範圍的值加起來除 2,得到中央值,
那我們可以這樣完成式子:
int val_up, val_down, val_lock;
while(1)
{
lock_val--;
if ( detect(val_lock) )
break;
}
val_up=val_lock;
val_lock-=20;
while(1)
{
lock_val++;
if ( detect(val_lock) )
break;
}
val_down=val_lock;
val_lock=(val_up+val_down)/2;
最後我們得到一個 val_lock 值,是我們所需要的,
在以上的範例中並不是 PLL 的實際應用,而是調頻器的應用,
但是程式的部份是相同以及觀念部份是相同的,而把頻率改為相位,
超前或是落後,就是 PLL 的實際應用,
DRAM 的頻率調整也類似這樣的方式,程式中的 P 相位我們可以看成是 DETECT() 而 L 鎖定則是 IF( DETECT(LOCK) ) 最後環 L 就是 WHILE(1)
是有點奇怪啦... 總之... 就醬子~

2014年9月24日 星期三

The Software for Minimal I2C

http://boldinventions.com/index.php?option=com_content&view=article&id=52:i2csimpleimplement&catid=34:category-electronics-articles&Itemid=53

The Software for Minimal I2C 

The Software for Minimal I2C 

Are you an I2C noob?  If so, check out this article before trying to port the source code.  
Before we actually get into the software, these diagrams show how the bus is hooked up to the CPU.
I2C Connection to a CPUIf the CPU has the capability of driving open collector output lines and reading their state directly, then the I2C hardware connection couldn't be easier. The 5k pullup resistors make the two bus lines float high if no device on the bus is pulling them low. There is a tradeoff between the resistor value and the capacitance load on the lines. If the resistance is too high, then more devices on the bus tend to increase capacitance, and the signals are less square, leading eventually to failures. If the resistance is too low, then the weakest devices on the bus will not be able to sink enough current to drive the lines to logical 0.





I2C Seperate In and Out ConnectionsIf open collector lines are not available, it would be possible to use 2 outputs, and 2 input lines to accomplish the same thing. The diodes simply prevent the Out0 and Out1 output lines from driving the I2C lines high.






C - language Source code for Minimal I2C

The following source code is an example of a minimal I2C implementation.  It was actually translated from FORTH, so this may contain some small mistakes.

CPU-Dependent Primitives

The following bits are implementation-dependent code, which will vary depending on which CPU is being used.

#define SCLOUT( val )      set_p0_state( val )    /* CPU Dependent function to set p0 to low-state val=0, or float val=1 */
#define SDAOUT( val )      set_p1_state( val )  
#define SCL_IN             get_p0_state()         /* Returns 1 if p0 is high, or 0 if p0 is low. */
#define SDA_IN             get_p1_state()

#define MSEC_DELAY(usec)   cpu_dependent_delay(usec)  /* Waste time for some microseconds (not milliseconds) */

typedef unsigned char BYTE
 

Initialization

The first software step is to initialize the io ports to be open collector outputs, and then put the bus into an idle state. An idle state is where both lines are high. It may also be a good idea to generate aSTOP condition on the bus before putting it idle.
void i2c_setup()
{
    set_p0_open_collector();   /* Implementation dependent - must be made to match your CPU */
    set_p1_open_collector();

    SCLOUT(0);       /* Generate a STOP condition */
    SDAOUT(0);
    SCLOUT(1);       /* SCL is high */
    MSEC_DELAY(10);  /* Wait */
    SDAOUT(1);       /* Now cause a low-to-high transistion on SDA */
}

I2C Start and Stop Conditions

The following code implements the I2C Start and Stop conditions using the primitives defined above.
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\ void i2c_do_stop(void)   
\\  puts data low, then clock low,
\\  then clock high, then data high.
\\  This should cause a stop, which
\\  should idle the bus, I.E. both clk and data are high.
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

void i2c_do_stop(void)
{
  SCLOUT(0);
  MSEC_DELAY(10);
  SDAOUT(0);
  MSEC_DELAY(10);
  SCLOUT(1);
  MSEC_DELAY(10);
  SDAOUT(1);
}

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\ void i2c_do_start(void)
\\   Sets clock high, then data high.  This will do a stop if data was low.
\\   Then sets data low, which should be a start condition.
\\   After executing, data is left low, while clock is left high
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
void i2c_do_start(void)
{
   SCLOUT(1);
   SDAOUT(1);
   MSEC_DELAY(10);
   SDAOUT(0);
   MSEC_DELAY(10); \\ waste time
}

 
 Next - More I2c Source Code


 
Last Updated on Sunday, 17 May 2009 13:52

2014年9月19日 星期五

[基礎程式語法]第十四章 副程式與函式

參考:http://home.educities.edu.tw/mariorpg/C++/c114.html

1.副程式概論:
之前我們寫的程式都在主程式(main)的範圍內,不過完整的程式檔案可不只它而已,這章將介紹主程式的幫手-副程式!

副程式跟主程式一樣的重要,差別只在於它不會像主程式一樣的自動執行,在呼叫(主程式或別的副程式皆可)時才會去執行。其實,我們之前寫的程式中已經用過副程式了,只是那些副程式都是C語言中的庫存函式(Compiler中內見的),所以現在我們就來寫寫屬於自己的副程式吧!

副程式的主要用途為將一些常用的程式碼,獨立拿出來寫成另一大段的程式碼,當主程式或其它副程式需要用到時,便可呼叫副程式來使用,而不需再去copy這一段程式碼。此種方法為「物件導向(OPP)」的一種,之後的章節也會以類似的方法來寫程式。

1.副程式的寫法:
和主程式很類似,main改成副程式的名子,且必須寫在主程式的上方!
注意:傳回值一定要填寫,否則會出現編譯錯誤!

傳回型態 副程式名(引數內容)
{
  副程式的內容
}

2.呼叫副程式:
副程式名();  //直接輸入名稱後加上()即可!

範例:寫一個歡迎的副程式,並呼叫它!
#include <stdio.h>
#include <iostream.h>

void come(void)   //副程式come:內容為顯示歡迎的訊息
{
printf("歡迎進入C++的副程式!\n");
}

int main(void)
{
int x;

printf("請輸入一個數並按Enter來呼叫副程式:\n");
cin>>x;
come();  //呼叫副程式come

  system(pause);
  return 0;
}

3.副程式也可呼叫其它的副程式:
不過必需注意一點,被呼叫的副程式必須寫在叫它的副程式前面,不然會出現編譯上的錯誤!
:副程式A去呼叫副程式B,B就必須寫在A的前面才行!

範例:先以主程式呼叫副程式a,再以副程式a去呼叫副程式b
#include <stdio.h>
#include <iostream.h>

void b(void)
{
printf("我是副程式b!\n");
}

void a(void)
{
printf("我是副程式a!\n");
b();      //呼叫副程式b
}

int main(void)
{
a();     //呼叫副程式a
system("pause");
}

4.副程式呼叫自己:
副程式除了呼叫主副程式外,還可以呼叫自己,此動作稱為遞迴

範例:寫一個副程式來計算階乘(n!)
#include < stdio.h >

int fact(int n)
{
if(n<=1)
return 1;
return n*fact(n-1);     //n!=n*(n-1)!
}

void main(void)
{
int n;
printf(“請輸入階乘的數(n):\n);
scanf("%d",&n);
printf("%d!=%d\n",n,fact(n));
}

2.全域與區域變數:
變數如果只宣告在某個副程式或主程式中,就只能在該程式內使用,但如果將變數宣告在外面,即可從宣告那行之後任一行使用!

#include <stdio.h>

int x;    //從宣告的這行後都可使用

void number(void)
{
x=x+5;
}

int main(void)
{
number();
printf("%d\n",x);
}

1.全域與區域變數:
如果變數在某個主副程式內宣告,則為該程式的區域變數,若是在全部程式的上方宣告,則為全域變數想顯示全域變數時,在變數前方加上::即可!

#include < stdio.h >

int a=5;     //這個a為全域變數,可供任何主副程式使用!

void file(void)
{
int a=3;    //這個a為區域變數,只能在此副程式內使用!
printf("%d\n",a);        //顯示區域變數的a,a=5
printf("%d\n",::a);      //顯示全域變數的a,a=3
}

void main(void)
{
file();
}

2.全域變數的陣列:
函式中的變數都是放在記憶體的stack區段,區段容量通常都不大,如果宣告了一個太大的陣列就會把stack都佔滿,很有可能一執行就當機,此時就需要把陣列由主副程式中移到外面變成全域的陣列:

1.宣告方式: #define 陣列指標 (x)*(y)*(z)
2.套用方式陣列名稱[陣列指標]

範例:宣告一個全域陣列來使用
#include <stdio.h>
#define N 1024*1024

int a[N];       //宣告陣列a成全堿變數,a不再放在stack.

void main(void)
{
}

3.函式的用法:
雖然副程式很好用,但是卻有個很大的缺點-主副程式的參數與變數不一致!
簡單說就是假設主程式有個變數i想代入副程式計算,副程式接收的i與主程式傳入的i兩者是不同的,因此當副程式傳回給主程式i時會發現i並未作運算,因此想作這方面的事則必須使用另一種副程式-函式,而函式的使用分為三個步驟:

1.函式的宣告:
首先必需在主程式main的上方宣告函式的原型,宣告的方式如下:
引數內容為資料型態 + 資料的變數,可同時代入多種不同型態的引數,:(int x,float y,char a)

傳回型態 函式名稱(引數內容);  //引數內容即是代入函式作處理的資料

2.函式的定義:
寫在主程式main的下方,就是指函式的處理內容,寫的方式與副程式一樣,只不過要加上return來傳回:

傳回型態 函式名稱(引數內容)
{
  函式內容

  return 傳回變數;
}

3.函式的使用:
最後便是在主程式中使用該函式了,只要輸入名稱然後在括弧內輸入引數即可:

函式名稱(引數內容);

範例:以副程式將x=3代入函式g(x)=x+5計算後,再代回主程式顯示!
#include <stdio.h>
#include <iostream.h>

void g(x);

int main(void)
{
int x=3;
int answer; 

answer=g(x);
cout<<answer<<endl;
      
system("pause");
return 0;  
}

int g(int x)
{
x=x+5;

return x;
}

4.函式的傳回值:
常見的函式傳回值宣告法,有下列六種:

1.void函式:
如果函式的內容是不需傳回主程式作運算的(多半為文字類訊息),則需將資料型態設為void:

void 函式名稱(引數內容)    //如果是文字類訊息,引數內容則為空白

2.int函式:
將資料型態設為int,即傳回整數的資料;如果傳入的資料經計算為浮點數或字元,則將自動轉為整數傳回

int 函式名稱(引數內容)

3.long函式:
當傳入的整數資料過大,超出-32670~32670的範圍時,則可使用這種資料型態來宣告函式

long 函式名稱(引數內容)

4.double函式:
將資料型態設為double,即傳回雙精浮點數的資料,此資料型態包函了浮點數(float)與整數(int)

double 函式名稱(引數內容)

5.char函式:
將資料型態設為char,即傳回單字元的資料,:A,B,C

char 函式名稱(引數內容)

6.bool函式:
如果想將函式作邏輯上的判斷,則可使用這種型態的函式

bool 函式名稱(引數內容)    //此種函式的傳回值(return)只能為trueflase