PIC cc5xでLCDを使ってみる
LCD(液晶ディスプレイ)を使えると表示できる情報量が一気に増えて
とても便利になります。
と言う事で今回はPIC 16F648Aとcc5x CコンパイラでLCDを使ってみようと思います。
使用したLCDはこちら
秋月で購入した2行×16文字表示のLCDです。
作成した回路がこれ

RB1をLCDのRSに、RB2をLCDのEに繋げ
RA0~3をLCDのDB4~7に繋いで4ビット動作させます。
LCDはコマンドの処理などに数十μ秒から数十ミリ秒までの待機時間を必要とするので
そのための関数を作ります。
もしかしたらcc5xに元から入っているかもしれませんが探すよりも作るほうが楽ですしね。
今回16F648Aを内蔵オシレータで動かすので動作クロックは4MHzとなります。
そうすると1クロック当たり0.25μ秒かかり、1サイクルが4クロックなので
1つの命令を実行するのに約1μ秒かかることになります。
これを用いて、数十μ秒単位でディレイさせる関数を作ります。
作ったのがこれ
吐き出されるアセンブラリストを見る限りでは10サイクル程度なので
10μ秒遅延させることができるはずです。
で、これを
こういうロジックを用いてRB0から出力させて
波形をオシロで見てみます
と

このように約39μ秒のパルスが出ました。
それでもって
このようにsleepさせずに波形を出力すると

このように約11μ秒の波形が出ます。
と言う事で先のsleep関数は10μ秒眠らせるはずが
28μ秒眠ってしまうようです。
ちなみに
このようにnop()を入れると

このように、ちゃんと1μ秒遅延するようです。
とりあえずこれで数十μ秒眠らせる関数はできたので実際にLCDを制御してみます。
これがソース
今回使っていない変数など色々入っていますが
拡張性を考えてのことです。
さて、説明ですが
LCDは最初に初期化をしないと何もできません
ですので初期化をします。
上記のソースのlcd_init()と言う関数で行っています。
このLCDはデータシートを見る限りでは電源投入後40ms以上待機させる必要があるらしいので
sleep_us関数で42ms眠らせます。
LCDは待機時間が短いと正常動作しませんが待機時間が長い分には問題ありません
上記の工程でLCDを一度8ビットモードにしています。
この工程で4ビットモードに移行します。
この工程で2行表示モードにします。
2行or1行、フォントサイズ5x11 or 5x8を選べます。
この工程でカーソルのON/OFF等を設定します。
ここでEntry Modeの設定をします
インクリメント1、ノーシフトにしておけば大体大丈夫のはず。
これは必要ない処理ですがカーソル位置を左上に指定しています。
}
LCDはRSを0にしてデータを送るとコマンドとして認識され
RSを1にしてデータを送ると文字データとして認識されます。
コマンドや文字データの取り込みにはEフラグが1から0に切り替わる
立下りエッジを使っていますのでデータビットの上位4ビット
下位4ビットを送る際にそれぞれ
RB2 = 1;
nop();
RB2 = 0;
この処理を入れてデータビットをLCD内に取り込ませています
立下りエッジの認識に220nsの待機時間が必要です。
内蔵オシレータの4MHzなんて言う低速で動かす分には必要ありませんが
20MHzとかで動かすとこのタイミングもシビアになってくるので
nop()を入れておきます。
と言うかcc5xだとnop()入れないとワーニング吐きます・・・・・・
途中途中で数十μ秒から数m秒待機させていますが
このあたりの待機時間が短いと初期化で失敗するのでデータシート表記よりも長めに取っておくと吉です。
とりあえず、これで初期化が完了ですので
LCDに文字を表示させてみましょう。
その前に
こう言う関数を作りました
LCDの文字表示位置を指定する関数です。
0x08で1行目0x0cで2行目表示になり
次の4ビットで0から15の数字を指定して表示位置を決めます。
でもって
この関数で実際に文字列を表示させます。
表示させる文字列をポインタで受け取り
1文字ずつ表示させるだけの関数です。
で
lcd_init();
lcd_pos(1, 0); // LCDの1行目0桁目
lcd_write("Hello world!!");
lcd_pos(2, 1);
lcd_write("Welcome to pic");
メインルーチンのこの部分で
実際に文字列を表示させます。
結果がこちら

うまくいきました。
LCDが使えると表示する情報量が飛躍的に増える上に
picの入出力ポートも最低6本のポートしか使用しないので重宝します。
LCD自体も安価に手に入りますし
これだけで工作の幅が一気に広がります。
とても便利になります。
と言う事で今回はPIC 16F648Aとcc5x CコンパイラでLCDを使ってみようと思います。
使用したLCDはこちら
秋月で購入した2行×16文字表示のLCDです。
作成した回路がこれ

RB1をLCDのRSに、RB2をLCDのEに繋げ
RA0~3をLCDのDB4~7に繋いで4ビット動作させます。
LCDはコマンドの処理などに数十μ秒から数十ミリ秒までの待機時間を必要とするので
そのための関数を作ります。
もしかしたらcc5xに元から入っているかもしれませんが探すよりも作るほうが楽ですしね。
今回16F648Aを内蔵オシレータで動かすので動作クロックは4MHzとなります。
そうすると1クロック当たり0.25μ秒かかり、1サイクルが4クロックなので
1つの命令を実行するのに約1μ秒かかることになります。
これを用いて、数十μ秒単位でディレイさせる関数を作ります。
/********************************************************************/
/* */
/* sleep_us(unsigned long) */
/* 指定マイクロ秒数ループ */
/* 1サイクル4クロック、1クロック=0.25μs 1回のループでasmソース上おおよそ */
/* 10サイクル使用するので約10μsのはずが実測28μs */
/* 20-02/13 */
/********************************************************************/
void sleep_us(unsigned long t)
{
while(t) {
t--;
}
}
作ったのがこれ
吐き出されるアセンブラリストを見る限りでは10サイクル程度なので
10μ秒遅延させることができるはずです。
で、これを
while (1){
sleep_us(1);
if (g_flg == 1){
RB0 = 1;
g_flg = 0;
}else{
RB0 = 0;
g_flg = 1;
}
}
こういうロジックを用いてRB0から出力させて
波形をオシロで見てみます
と

このように約39μ秒のパルスが出ました。
それでもって
while (1){
if (g_flg == 1){
RB0 = 1;
g_flg = 0;
}else{
RB0 = 0;
g_flg = 1;
}
}
このようにsleepさせずに波形を出力すると

このように約11μ秒の波形が出ます。
と言う事で先のsleep関数は10μ秒眠らせるはずが
28μ秒眠ってしまうようです。
ちなみに
while (1){
if (g_flg == 1){
RB0 = 1;
nop();
g_flg = 0;
}else{
RB0 = 0;
g_flg = 1;
}
}
このようにnop()を入れると

このように、ちゃんと1μ秒遅延するようです。
とりあえずこれで数十μ秒眠らせる関数はできたので実際にLCDを制御してみます。
/********************************************************************/
/* */
/* PICテスト用プログラム */
/* 作成者:M.Sato */
/* 作成日:20年2月13日 */
/* 概要 :cc5xでLCD表示プログラム作成 */
/* device:16F648A */
/* */
/********************************************************************/
#include "..\int16cXX.h"
#pragma config = 0X3F18 // //OSC:内蔵クロック WDT:OFF PUT:OFF MCLRE:無効(RA5入力ピンとして使用)
//ブラウンアウトリセット:禁止 LVP:禁止 CP、CPD:プロテクト オフ
// 11111100011000
#pragma origin 4 //割り込みベクターアドレスを&h04に指定
#pragma chip PIC16F648A
#pragma bit RA0 @ 5.0
#pragma bit RA1 @ 5.1
#pragma bit RA2 @ 5.2
#pragma bit RA3 @ 5.3
#pragma bit RA4 @ 5.4
#pragma bit RA5 @ 5.5
#pragma bit RA6 @ 5.6
#pragma bit RA7 @ 5.7
#pragma bit RB0 @ 6.0
#pragma bit RB1 @ 6.1
#pragma bit RB2 @ 6.2
#pragma bit RB3 @ 6.3
#pragma bit RB4 @ 6.4
#pragma bit RB5 @ 6.5
#pragma bit RB6 @ 6.6
#pragma bit RB7 @ 6.7
#define TRUE 1
#define FALSE 0
unsigned long g_tmr0cnt;
int g_tmr0flg, g_flg;
/********************************************************************/
/* */
/* 割り込み処理 */
/* */
/* 20-02/13 */
/********************************************************************/
interrupt int_serverX( void)
{
int_save_registers // W STATUS PCLATHを保存
if (T0IF) { // タイマー0オーバーフロー 1クロック=0.25μs TMR0カウント=4クロック=1μs TMR0オーバーフロー256クロック=256μs
g_tmr0cnt++;
g_flg = 1;
T0IF = 0; // reset flag
}
int_restore_registers // W STATUS PCLATHを復帰
}
/********************************************************************/
/* */
/* sleep_us(unsigned long) */
/* 指定マイクロ秒数ループ */
/* 1サイクル4クロック、1クロック=0.25μs 1回のループでasmソース上おおよそ */
/* 10サイクル使用するので約10μsのはずが実測28μs */
/* 20-02/13 */
/********************************************************************/
void sleep_us(unsigned long t)
{
while(t) {
t--;
}
}
/********************************************************************/
/* */
/* LCD初期化処理 */
/* RS=RB1 E=RB2 */
/* 2020-02/16 */
/********************************************************************/
void lcd_init()
{
sleep_us(1500); // 電源ON後約40ms待機
RB1 = 0;
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x03; // function set
RB2 = 1;
nop();
RB2 = 0;
sleep_us(150); // 約4.2ms待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x03; // function set
RB2 = 1;
nop();
RB2 = 0;
sleep_us(5); // 140us待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x03; // function set
RB2 = 1;
nop();
RB2 = 0;
sleep_us(150); // 4.2ms待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x02; // 4ビットモードに移行
RB2 = 1;
nop();
RB2 = 0;
sleep_us(3); // 84us待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x02; // 2行表示モードに設定
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x08;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(3); // 84us待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00; // ディスプレイON,カーソルON,ブリンクONに設定
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x0F;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(3); // 84us待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00; // ディスプレイクリア
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x01;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(180); // 約5ms待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00; // entry mode
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x06;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(2); // 84us待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x08; // カーソル位置設定
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(65); // 約1.8ms待機
}
/********************************************************************/
/* */
/* LCD表示処理 */
/* RS=RB1 E=RB2 */
/* 2020-02/17 */
/********************************************************************/
void lcd_write(const char *str)
{
char c;
while (*str != '\0'){
RB1 = 1; // RS = 1
c = *str >> 4;
PORTA &= 0xF0; // RA0~RA3を0クリア
PORTA |= c; // データの上位4ビットをRA0~RA3にセット
RB2 = 1;
nop();
RB2 = 0;
c = 0;
c = *str & 0x0F;
PORTA &= 0xF0; // RA0~RA3を0クリア
PORTA |= c; // データの下位4ビットをRA0~RA3にセット
RB2 = 1;
nop();
RB2 = 0;
sleep_us(6);
str++;
}
}
/********************************************************************/
/* */
/* LCD表示クリア処理 */
/* RS=RB1 E=RB2 */
/* 2020-02/17 */
/********************************************************************/
void lcd_clr()
{
RB1 = 0;
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00; // ディスプレイクリア
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x01;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(500); // 5ms待機
}
/********************************************************************/
/* */
/* LCD表示位置変更処理 */
/* void lcd_pos(int line, int column) */
/* lne 1:1行目 2:2行目 */
/* clm 文字表示位置 0~15 */
/* RS=RB1 E=RB2 */
/* 2020-02/17 */
/********************************************************************/
void lcd_pos(int lne, int clm)
{
RB1 = 0;
RB2 = 0;
if (lne == 1){
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x08;
RB2 = 1;
nop();
RB2 = 0;
if (clm < 0 || clm > 15){
clm = 0;
}
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= clm;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(2);
}else{
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x0c;
RB2 = 1;
nop();
RB2 = 0;
if (clm < 0 || clm > 15){
clm = 0;
}
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= clm;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(2);
}
}
/********************************************************************/
/* */
/* メインルーチン */
/* RS=RB1 E=RB2 */
/* 2020-02/13 */
/********************************************************************/
void main()
{
long x;
long y;
long i;
int vw[3];
unsigned long cnt, cnt1, cnt2, cnt3, n, tmp;
int j;
char str[20];
char c, dat;
OSCF = 1; // 内蔵4MHz
TRISA = 0x00; // ポートAは全て出力
TRISB = 0x00; // ポートBは全て出力
CMCON = 0x07; //アナログコンパレータをOFF。
OPTION = 0xc8; // 内蔵プルアップを使用しない
// RB0立ち上がりエッジ
// タイマー0は内部クロック使用
// プリスケーラはWDTへ割り当て
g_tmr0cnt = 0;
g_tmr0flg =0;
g_flg = 0;
GIE = 1; // 全ての割り込み発生を許可
T0IE = 1; // TMR0割り込みを許可
lcd_init();
lcd_pos(1, 0); // LCDの1行目0桁目
lcd_write("Hello world!!");
lcd_pos(2, 1);
lcd_write("Welcome to pic");
}
これがソース
今回使っていない変数など色々入っていますが
拡張性を考えてのことです。
さて、説明ですが
LCDは最初に初期化をしないと何もできません
ですので初期化をします。
上記のソースのlcd_init()と言う関数で行っています。
/********************************************************************/
/* */
/* LCD初期化処理 */
/* RS=RB1 E=RB2 */
/* 2020-02/16 */
/********************************************************************/
void lcd_init()
{
sleep_us(1500); // 電源ON後約40ms待機
このLCDはデータシートを見る限りでは電源投入後40ms以上待機させる必要があるらしいので
sleep_us関数で42ms眠らせます。
LCDは待機時間が短いと正常動作しませんが待機時間が長い分には問題ありません
RB1 = 0;
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x03; // function set
RB2 = 1;
nop();
RB2 = 0;
sleep_us(150); // 約4.2ms待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x03; // function set
RB2 = 1;
nop();
RB2 = 0;
sleep_us(5); // 140us待機
上記の工程でLCDを一度8ビットモードにしています。
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x03; // function set
RB2 = 1;
nop();
RB2 = 0;
sleep_us(150); // 4.2ms待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x02; // 4ビットモードに移行
RB2 = 1;
nop();
RB2 = 0;
sleep_us(3); // 84us待機
この工程で4ビットモードに移行します。
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x02; // 2行表示モードに設定
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x08;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(3); // 84us待機
この工程で2行表示モードにします。
2行or1行、フォントサイズ5x11 or 5x8を選べます。
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00; // ディスプレイON,カーソルON,ブリンクONに設定
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x0F;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(3); // 84us待機
この工程でカーソルのON/OFF等を設定します。
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00; // ディスプレイクリア
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x01;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(180); // 約5ms待機
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00; // entry mode
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x06;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(2); // 84us待機
ここでEntry Modeの設定をします
インクリメント1、ノーシフトにしておけば大体大丈夫のはず。
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x08; // カーソル位置設定
RB2 = 1;
nop();
RB2 = 0;
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x00;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(65); // 約1.8ms待機
これは必要ない処理ですがカーソル位置を左上に指定しています。
}
LCDはRSを0にしてデータを送るとコマンドとして認識され
RSを1にしてデータを送ると文字データとして認識されます。
コマンドや文字データの取り込みにはEフラグが1から0に切り替わる
立下りエッジを使っていますのでデータビットの上位4ビット
下位4ビットを送る際にそれぞれ
RB2 = 1;
nop();
RB2 = 0;
この処理を入れてデータビットをLCD内に取り込ませています
立下りエッジの認識に220nsの待機時間が必要です。
内蔵オシレータの4MHzなんて言う低速で動かす分には必要ありませんが
20MHzとかで動かすとこのタイミングもシビアになってくるので
nop()を入れておきます。
と言うかcc5xだとnop()入れないとワーニング吐きます・・・・・・
途中途中で数十μ秒から数m秒待機させていますが
このあたりの待機時間が短いと初期化で失敗するのでデータシート表記よりも長めに取っておくと吉です。
とりあえず、これで初期化が完了ですので
LCDに文字を表示させてみましょう。
その前に
/********************************************************************/
/* */
/* LCD表示位置変更処理 */
/* void lcd_pos(int line, int column) */
/* lne 1:1行目 2:2行目 */
/* clm 文字表示位置 0~15 */
/* RS=RB1 E=RB2 */
/* 2020-02/17 */
/********************************************************************/
void lcd_pos(int lne, int clm)
{
RB1 = 0;
RB2 = 0;
if (lne == 1){
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x08;
RB2 = 1;
nop();
RB2 = 0;
if (clm < 0 || clm > 15){
clm = 0;
}
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= clm;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(2);
}else{
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= 0x0c;
RB2 = 1;
nop();
RB2 = 0;
if (clm < 0 || clm > 15){
clm = 0;
}
PORTA &= 0xF0; // PORTAの下位4ビットを0クリア
PORTA |= clm;
RB2 = 1;
nop();
RB2 = 0;
sleep_us(2);
}
}
こう言う関数を作りました
LCDの文字表示位置を指定する関数です。
0x08で1行目0x0cで2行目表示になり
次の4ビットで0から15の数字を指定して表示位置を決めます。
でもって
/********************************************************************/
/* */
/* LCD表示処理 */
/* RS=RB1 E=RB2 */
/* 2020-02/17 */
/********************************************************************/
void lcd_write(const char *str)
{
char c;
while (*str != '\0'){
RB1 = 1; // RS = 1
c = *str >> 4;
PORTA &= 0xF0; // RA0~RA3を0クリア
PORTA |= c; // データの上位4ビットをRA0~RA3にセット
RB2 = 1;
nop();
RB2 = 0;
c = 0;
c = *str & 0x0F;
PORTA &= 0xF0; // RA0~RA3を0クリア
PORTA |= c; // データの下位4ビットをRA0~RA3にセット
RB2 = 1;
nop();
RB2 = 0;
sleep_us(6);
str++;
}
}
この関数で実際に文字列を表示させます。
表示させる文字列をポインタで受け取り
1文字ずつ表示させるだけの関数です。
で
lcd_init();
lcd_pos(1, 0); // LCDの1行目0桁目
lcd_write("Hello world!!");
lcd_pos(2, 1);
lcd_write("Welcome to pic");
メインルーチンのこの部分で
実際に文字列を表示させます。
結果がこちら

うまくいきました。
LCDが使えると表示する情報量が飛躍的に増える上に
picの入出力ポートも最低6本のポートしか使用しないので重宝します。
LCD自体も安価に手に入りますし
これだけで工作の幅が一気に広がります。
この記事へのコメント