今回はPICマイコンのコンパイラ仕様で沼ったので注意点をメモしておきます。
データシートにそれっぽいことは書いてありますが、原因ははっきりわかってないので参考程度にお願いします。
備忘録。
発生した問題:PIC16F1827にてポートの電圧が正しく出力されない
まずは今回発生した問題からまとめてみます。
PIC16F1827にて、RA1ポートを高速でHやLに切り替えようとした際、なぜか出力がパルス状になってしまう問題が起こりました。
検証用ソースコードは下記になります。
static void main_init(void)
{
/* functional initialize */
INTCON = 0x00;
OSCCON = 0xF0;
OSCTUNE = 0x00;
OPTION_REG = 0xC7;
APFCON0 = 0x08;
APFCON1 = 0x00;
/* port initialize */
TRISA = 0x00;
PORTA = 0x00;
TRISB = 0x00;
PORTB = 0x00;
}
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
/* このコードを実行すると、出力 RA1 がパルス状になる */
PORTAbits.RA7 = CLEAR;
PORTAbits.RA6 = CLEAR;
PORTAbits.RA5 = CLEAR;
PORTAbits.RA4 = CLEAR;
PORTAbits.RA3 = CLEAR;
PORTAbits.RA2 = CLEAR;
PORTAbits.RA1 = SET;
PORTAbits.RA0 = CLEAR;
}
}
コードはいたって単純。
PORTAの0~7のビットにそれぞれHレベル、Lレベルを設定しているだけのコードです。
ところがこのソースをコンパイルして書き込むと、出力をHレベルに設定したRA1のポート出力が以下のようになります。
なぜか、パルス状の出力になっています。
今回使用しないタイマや通信機能のレジスタは未設定(初期値のまま)です。
勝手に割込みなどが発生するとは考えられず、実際にちゃんとレジスタ設定(無効にするコードを書く)を行っても解決しませんでした。
問題を解決できるソースコード
出力波形がパルスになる問題を解決するには、以下のようにソースコードを変更します。
static void main_init(void)
{
/* functional initialize */
INTCON = 0x00;
OSCCON = 0xF0;
OSCTUNE = 0x00;
OPTION_REG = 0xC7;
APFCON0 = 0x08;
APFCON1 = 0x00;
/* port initialize */
TRISA = 0x00;
PORTA = 0x00;
TRISB = 0x00;
PORTB = 0x00;
}
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
/* このコードを実行すると、出力RA1はパルスにならない */
PORTA = 0x02;
}
}
処理内容は変わっていません。
変わったところは、標準ライブラリである"PIC16F1827.h"で定義されたビットフィールド(1ビット直接操作用)を使っていない点です。
実際の波形は以下のようになります。
波形を見ながら原因を整理してみる
プログラムと波形を照らし合わせて、問題の詳細を調べてみます。
No.1:1つのポートをHレベル➡Hレベルに上書きし続ける場合
今回は問題が起こったRA1ポートを中心に検証してみます。
プログラムは下記の通り。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA1 = SET; ※SET=1
}
}
このソースの実行波形は以下のようになります。
特にパルス状になることもなく、一定のポート出力が得られました。
No.2:2つのポートの出力をH➡Hに上書きし続ける場合
次に2つのポートをH➡Hに上書きし続ける場合を試してみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA1 = SET;
PORTAbits.RA0 = SET;
}
}
実行結果は以下のようになりました。
出力波形がパルス状になりました。
RA0、RA1両方ともHレベル➡Hレベルへの書き換えですが、一瞬Lに落ちています。
No.3:ビットに直接1、0を書き込んだ場合
今回はSET(1)、CLEAR(0)というマクロを定義して使用していましたが、これが悪さしている可能性もあるので、以下のように直接数字を書き込み形にソースを修正してみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA1 = 1;
PORTAbits.RA0 = 1;
}
}
実行結果は以下の通り。
特に変化はないですね。
No.4:1つのポートをLレベルに、1つのポートをHレベルに上書きする場合
次はポートの状態をH、Lに上書きする場合を試してみます。
まずはオシロで見ていないRA0ポートをLレベルにしてみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA1 = SET;
PORTAbits.RA0 = CLEAR;
}
}
実行結果は以下の通り。
ほぼ変わっていないように見えますが、よ~く見ると、H状態の時に揺れが穏やかになってます。
意味不明。
次はオシロで測定中のポートをLレベルにした場合を試してみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA1 = CLEAR;
PORTAbits.RA0 = SET;
}
}
実行結果は以下の通り。
Lに設定しているポートが、ほかのポートの設定によりHに変化してしまう、ということはないようです。
No.5:3つのポートを設定した場合
次は3つのポートの状態を設定した場合を試してみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA2 = CLEAR;
PORTAbits.RA1 = SET;
PORTAbits.RA0 = CLEAR;
}
}
実行結果は以下の通り。
パルスのHレベル区間が短くなり、Lレベル期間が長くなりました。
パルスが立つ周期自体はあまり変わっていないように見えます。
No.6:多数のポートを同時に設定する場合:例1
次に、多数のポートを同時に設定した場合を確認してみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA7 = CLEAR;
PORTAbits.RA6 = CLEAR;
PORTAbits.RA5 = CLEAR;
PORTAbits.RA4 = CLEAR;
PORTAbits.RA3 = CLEAR;
PORTAbits.RA2 = CLEAR;
PORTAbits.RA1 = SET;
PORTAbits.RA0 = CLEAR;
}
}
実行結果は以下のようになりました。
周期が長くなっています。
書き換えの回数が増えたので1回のループ処理にかかる時間は長くなります。
この変化が波形にも表れる形になりました。
No.7:多数のポートを同時に設定する場合:例2
次はRA1以外にもう1ポートだけ、Hにするポートを入れてみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA7 = SET;
PORTAbits.RA6 = CLEAR;
PORTAbits.RA5 = CLEAR;
PORTAbits.RA4 = CLEAR;
PORTAbits.RA3 = CLEAR;
PORTAbits.RA2 = CLEAR;
PORTAbits.RA1 = SET;
PORTAbits.RA0 = CLEAR;
}
}
実行結果は・・・・以下のようになりました。
CH1がRA1、CH2がRA7ポートの測定波形になります。
ここで、RA7ポートは Hレベル ➡ Hレベル への上書きにも関わらず、出力がパルス状にならないことがわかりました。
一方で、RA1ポートは Hレベル ➡ Hレベル への上書きで、出力がパルス状になることがわかりました。
No.8:RA1以外のポートを操作してみる
上記の検証で、今回の出力がパルス状になる問題がポートRA1に依存した問題である可能性が出たので、
次はRA7ポートにフォーカスして、RA1ポートをLレベルの上書きに固定して検証してみます。
RA7がRA1以外のポートの変化を受けるのか (RA7もパルス状になるのか) を、確認してみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTAbits.RA7 = SET;
PORTAbits.RA6 = CLEAR;
PORTAbits.RA5 = CLEAR;
PORTAbits.RA4 = CLEAR;
PORTAbits.RA3 = CLEAR;
PORTAbits.RA2 = CLEAR;
PORTAbits.RA1 = CLEAR;
PORTAbits.RA0 = SET;
}
}
実行結果は以下の通り。
出力波形がパルス状になる問題は発生していません。
この後 RA0 ~ RA7 まで1、0を切り替えて検証しましたが、RA1が0に設定されているときは、いずれも出力がパルス状になる問題は発生しませんでした。
No.8:ライブラリのビット操作命令の使用をやめてみる
次はライブラリのビットフィールド変数である「PORTAbits.RAx」の使用をやめ、直接ビットに代入する形をとってみます。
ソースコードは以下の通り。RA7とRA0をビット代入で処理してみます。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{
PORTA |= 0x80;
PORTA |= 0x02;
}
}
実行結果は以下の通り。
No.2 の検証とほぼ同じ波形が得られました。
標準ヘッダファイルである「pic16f1827.h」で定義されたビットフィールドを使用する/しないは、RA1の出力がパルス状になる問題とは関係がないようです。
ポートへの出力をレジスタ全体で行ってみる
次はポートをビットフィールド変数を使用せずに上書きしてみます。
プログラムの内容的な意味は同じになります。
void main(void) {
main_init(); /* initialize before loop task. */
while(1)
{ /* 意味は同じ */
PORTA = 0x82;
/*PORTAbits.RA7 = SET; */
/*PORTAbits.RA6 = CLEAR; */
/*PORTAbits.RA5 = CLEAR; */
/*PORTAbits.RA4 = CLEAR; */
/*PORTAbits.RA3 = CLEAR; */
/*PORTAbits.RA2 = CLEAR; */
/*PORTAbits.RA1 = CLEAR; */
/*PORTAbits.RA0 = SET; */
}
}
実行結果は以下のようになりました。
RA1のポートの出力がパルス状になる問題がなくなりました。
現象のまとめ
今回の検証結果を一言でまとめてみると、以下のようになりました。
ポートRA1を1ビット操作命令で書き換得た場合、RA1以外のポートも1ビット操作命令で書き換えていると、出力波形がパルス状になる。
ポート全体をレジスタのビット長でまとめて書き直した場合、出力波形に異常は生じない。
罠過ぎるだろぉ。
考えられる原因
直接の原因をはっきり書きたい・・・ところなのですが、デバッガ等で確認しても目立った問題はありません。
データシートを確認すると、すべての書き込み操作はリード・書き換え・適用の3ステップで進んでいるようで、すべての書き込み操作はリード&モディファイになる、と記載があります。
これがビット操作、レジスタ全体の操作両方の場合に当てはまるのかは分かりませんが、コンパイラ上での解釈が変わってくる模様。
前に何かで、「PIC18Fシリーズはポート状態読み出し用のラッチが別個で用意されたため、読み出し操作時に誤って書き込まれてしまう問題が解決された」という旨の記事を読んだ気がします。
PIC16Fの汎用ポートは以下のようにポート出力レジスタを直接読み出しているので、なんか関係がありそうな気がします。
何でRA1だけほかのポートのビット命令の操作の影響を受けるのかはイマイチわからない感じです。
オブジェクトファイルをちゃんと読まないとわからなそう。
まとめ
今回はPIC16Fシリーズの特定のポートを1ビット操作命令で書き換えた場合に、ポートの出力がパルス状になってしまう問題をまとめてみました。
今回のように無限ループで何度も書き換え続ける、といったことはあまりないかと思いますが、高速の割込み処理でポートの状態を操作する場合にも同様の問題が起こりそうな気がします。
ネットにあるPIC16F1827のプログラムを見るとしっかりレジスタのビット長で、16進数で書かれているソースが多いです。なんでライブラリのビットフィールド変数便利なのに使わないのかな~と思っていたんですが、今回の問題でなんとなく納得。。。。。
これからはビットフィールド変数はレジスタ初期設定を行う場合にとどめ、通常処理ではちゃんとレジスタ全体で上書きするようにしようと思います。。。。
今回かなりシンプルなソースで検証したので、ビット操作命令をしている以外の原因はないかな~と思いますが、何か原因が分かったら修正・追記したいと思います。
それでは、また。