gccの復習
最近はarduinoばかりでC言語のプログラミングをほとんど忘れています。もう一度Cと取り組んでみたいと思い、その経過を書いてみようと思っています。CPUは主として ATmega328 DIP を Xtal 8MHz で使います。
0 環境と準備
1 LEDの点滅
2 LEDを光センサーとして使う
3 USARTの実験
4 LCD表示
5 I2C接続
6 インクルードファイル
avr gcc に思うこと
0 環境と準備 2024.03.08
PCはwindows10です。gccコンパイラは最新のものを使ってみると今まで使っていたmakefileでエラーが出ます。不勉強でmakefileを作ることができず、また、どこに不具合があるのかわかりません。
そこで以前に使っていた avr8-gnu-toolchain-3.5.4.1709 を使うことにしました。このファイルは c:\avr\ においています。
このgccは make.exe と rm.exe を含んでいませんので minGW から make.exe と rm.exe を \bin にコピーしています。
makefileとバッチファイルはこれを使うことにしています。
(2024.03.09) 色々と扱っているうちに最新版avr8-gnu-toolchain-3.7.0.1796-win32.any.x86_64が使えるようになりました。Microchipのこのページから AVR 8-Bit Toolchain (Windows) 3.7.0 をダウンロードしました。
本体を c:\avr\ に置き、binフォルダに make.exe と rm.exe をminGWからコピーして、このbinフォルダを環境変数PATHの最上位に書きました。これで、make clean、make all と書くだけでコンパイラが走ります。使えるようになった理由はわかりません。
(2024.04.04) ファイルが有るホルダーでコマンドプロンプトが出る機能が必要ですが、今のwin10にはありません。
webを探していましたら windows10右クリックに「コマンドプロンプトで開く」を追加する方法 がありましたので使わせていただきました。便利です。
1 LEDの点滅 2024.03.08
まず最初にLチカを試みました。PB0とPB1にLEDをつけて交互に点滅するだけです。
/* ***************************************************************************
* date: 2024.03.08
* program name: ledtest1 LED点滅プログラム
* cpu: DIP atmega328p
* Fcpu: 8MHz
* fuse: FF DE FD Xtal8MHz boot512B BOD2.7V
*
* pin15 PB1 ----220--LED--GND
* pin14 PB0 ----220--LED--GND
*
******************************************************************************* */
#define F_CPU 8000000UL // 最初に書く makefileに優先する delayを使うときは必要
#include <avr/io.h>
#include <util/delay.h>
int main(void){ /* void:空 引数なし */
DDRB=0b00000011; PORTB=0xff;
DDRC=0x00;
DDRD=0x00; PORTD=0xff; /* 入力設定後、highを書けばpullup */
for(;;){
PORTB=0b00000010;
_delay_ms(1000); // 1000ms;
PORTB=0b00000001;
_delay_ms(1000); // 1000ms
}
} /* mainルーチンの終わり */
プログラムの始めに書く#define F_CPU 8000000UL はmakefileのF_CPUに優先することがわかりました。delay.hを使うときはこれを必ず書いておかねばならないようです。
ポートの設定は、DDRxは1で出力 0で入力、PORTxは1でhigh 0でlow、PINxはデジタル入力、 です。
2 LEDを光センサーとして使う 2024.03.08
LEDは光起電力があります。特に赤色LEDは電圧が高い(1.5Vになることも)ようです。
LEDの起電力を調べるにはADCを使いますが、ADCはまだ経験していません。データシートを見ると多くのレジスタが関係していてどう処理をするべきか理解できません。
そこでwebを探していたら kumikomi-yitjc @ ウィキ に作成例が書かれていたのでそのままいただきました。
PC0に繋いだ赤色ledに強い光があたったときはPC3のledが消灯し、光が弱いときはledが点灯する仕組みです。
多くのレジスタが使われていますが、その意味は今後勉強したいと思うところです。今は、無事考えたとおりになりました。
/* ***************************************************************************
* date: 2024.03.08
* program name: adc01 ADの単発変換のサンプルプログラム
* cpu: DIP atmega328p
* Fcpu: 8MHz
* fuse: FF DE FD Xtal8MHz boot512B BOD2.7V
*
* pin23 PC0 ----220--LED(red)--GND センサー用
* pin26 PC3 ----220--LED--GND 確認用
*
******************************************************************************* */
#define F_CPU 8000000UL // 最初に書く makefileに優先する
#include <avr/io.h>
#include <util/delay.h>
int main()
{
unsigned int t;
// CLKPR = 0x80; CLKPR = 0; // 16MHz動作のための設定 理解できないので無効にした
DDRC = 0b00001000; // デバッグ用のLED(PC3)の設定
// ADCの初期化
ADMUX |= _BV(REFS0); // 電源5Vを使用、ADC0を使用, データ右詰め
ADCSRA |= _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
// ADEN, ADSC, CK/128
ADCSRA |= _BV(ADSC); // ADをスタート
while(1) {
if (ADCSRA & _BV(ADIF)) { // AD変換の終了を確認
ADCSRA |= _BV(ADIF);
t = ADC; // ADCは16bit幅で、8bitずつ2回に分けてアクセスされるので、
// 変数アクセス中に割り込みが発生しないように注意すること
if (t>300) {
PORTC = 0b00000000; // 明るければ消灯
} else {
PORTC = 0b00001000; // 暗ければ点灯
}
ADCSRA |= _BV(ADSC); // ADを再スタート
}
}
}
(2024.03.09) センサーLEDのADCデータ値(1024に対する)をserial通信でPCに送り、teratermで受けられるようにしました。
0.5秒のdelayを入れています。過去に作ったserial通信プログラムを応用しました。なお、serial受信は設定していません。
/* ***************************************************************************
* date: 2024.03.09
* program name: adc01 ADの単発変換のサンプルプログラム uartを追加
* cpu: DIP atmega328p
* Fcpu: 8MHz
* fuse: FF DE FD Xtal8MHz boot512B BOD2.7V
*
* pin23 PC0 ----220--LED(red)--GND センサー用
* pin26 PC3 ----220--LED--GND 確認用
* pin2 RX, pin3 TX uart
*
******************************************************************************* */
#define F_CPU 8000000UL // 最初に書く makefileに優先する
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h> // string
#include <avr/pgmspace.h>
#include <stdlib.h> //ltoa()が含まれる
/* プロトタイプ宣言 */
void string_out (char *msg_string); // UART送信
int main() /********************** MAIN ***************************************** */
{
unsigned int t;
char strg[7];
/* USART設定 mega328 */
UBRR0H = 0; /* 8MHz n=0(標準速非同期動作) ボーレート設定 */
UBRR0L = F_CPU/16/9600-1; /* 8MHz n=0 ボーレート設定 9600bsp=51 F_CPU/16/BOUD-1 */
UCSR0B = 0b10011000; /* _BV(RXEN)|_BV(TXEN)|_BV(RXCIE); 送受信許可 受信割込許可 */
UCSR0C = 6; /* フレーム形式設定(8ビット,1ストップビット) */
// CLKPR = 0x80; CLKPR = 0; // 16MHz動作のための設定 理解できないので無効にした
DDRC = 0b00001000; // デバッグ用のLED(PC3)の設定
// ADCの初期化
ADMUX |= _BV(REFS0); // 電源5Vを使用、ADC0を使用, データ右詰め
ADCSRA |= _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
// ADEN, ADSC, CK/128
ADCSRA |= _BV(ADSC); // ADをスタート
while(1) {
if (ADCSRA & _BV(ADIF)) { // AD変換の終了を確認
ADCSRA |= _BV(ADIF);
t = ADC; // ADCは16bit幅で、8bitずつ2回に分けてアクセスされるので、
// 変数アクセス中に割り込みが発生しないように注意すること
if (t>300) {
PORTC = 0b00000000; // 明るければ消灯
} else {
PORTC = 0b00001000; // 暗ければ点灯
}
itoa(t,strg,10);
string_out(strg);
_delay_ms(500);
ADCSRA |= _BV(ADSC); // ADを再スタート
}
}
}
/* ************************ 文字列送信ルーチン mega328 ***************************************************** */
void string_out (char *msg_string){ /* "*"で受けると、呼び出し側の配列の番地を受け取る */
uint8_t i;
i=0;
while (msg_string[i] !='\0'){
while ( !(UCSR0A & _BV(UDRE0)) ); //送信バッファの空きを待って
UDR0 = msg_string[i]; //送信データをセット
i++;
// _delay_ms(1);
}
while(!(UCSR0A & _BV(UDRE0))); UDR0 = 0x0d; _delay_ms(6); /* 送信バッファの空きを待ってデータをセット CRLF送信*/
while(!(UCSR0A & _BV(UDRE0))); UDR0 = 0x0a; _delay_ms(6); /* 送信バッファの空きを待ってデータをセット CRLF送信*/
}
3 USARTの実験 2024.03.10
シリアル通信の実験をしました。
送受信を調べるために、PCから文字を送って、アスキーに次の文字を返すことにしました。
/* ***************************************************************************
* date: 2024.03.10
* program name: nextletter UARTでPCから送った文字の次の文字を返す
* cpu: DIP atmega328p
* Fcpu: 8MHz
* fuse: FF DE FD Xtal8MHz boot512B BOD2.7V
* Vcc: 3.3V
*
* pin2 RX, pin3 TX uart
******************************************************************************* */
#define F_CPU 8000000UL // 最初に書く makefileに優先する
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h> // string
#include <avr/pgmspace.h>
#include <stdlib.h> //ltoa()が含まれる
volatile uint8_t rflg; // uart受信フラグ
volatile uint8_t rdat; // 受信データ 1byte
char recbuf[20]; // uart受信バッファ
volatile uint8_t rec_i=0; // rec_index
/* プロトタイプ宣言 */
void string_out (char *msg_string); // UART送信
void cr(); // send CRLF
/* 受信割込ルーチン */
ISR(USART_RX_vect){ /* データを読み出すとクリアされる rdat:char変数 */
recbuf[rec_i]=UDR0; // m328
if((recbuf[rec_i]=='\0')||(recbuf[rec_i]==0x0d)||(recbuf[rec_i]==0x0a)){
recbuf[rec_i]='\0';
rflg=1;
rec_i=0;
cli();
} else{
rec_i++;
}
}
int main() /********************** MAIN ***************************************** */
{
char s[20];
char msg[30];
uint8_t i,j;
/* USART設定 mega328 */
UBRR0H = 0; /* 8MHz n=0(標準速非同期動作) ボーレート設定 */
UBRR0L = F_CPU/16/9600-1; /* 8MHz n=0 ボーレート設定 9600bsp=51 F_CPU/16/BOUD-1 */
UCSR0B = 0b10011000; /* _BV(RXEN)|_BV(TXEN)|_BV(RXCIE); 送受信許可 受信割込許可 */
UCSR0C = 6; /* フレーム形式設定(8ビット,1ストップビット) */
while(1){
strcpy_P(msg,PSTR("type letters")); string_out(msg);cr();
rec_i=0;
sei();
while(rflg==0){
if(rflg==1){break;}
}
rflg=0;
for(i=0;i<20;i++){s[i]=recbuf[i];} // strcpy(s,recbuf); //
string_out(" in_letters: ");
string_out(s);
cr();
for(j=0;recbuf[j]!='\0';j++) recbuf[j]+=1;
string_out("out_letters: ");
string_out(recbuf);
string_out(" ");
cr();
cr();
}
}/* *************************** MAINルーチンの終わり ***************************************************************/
/* ************************ 文字列送信ルーチン CRLFなし mega328 ***************************************************** */
void string_out (char *msg_string){ /* "*"で受けると、呼び出し側の配列の番地を受け取る */
uint8_t i;
i=0;
while (msg_string[i] !='\0'){
while ( !(UCSR0A & _BV(UDRE0)) ); //送信バッファの空きを待って
UDR0 = msg_string[i]; //送信データをセット
i++;
}
}
/* *********************** CRLF送信ルーチン ****************************************************/
void cr(void){
while(!(UCSR0A & _BV(UDRE0))); UDR0 = 0x0d; _delay_ms(6); /* 送信バッファの空きを待ってデータをセット CRLF送信*/
while(!(UCSR0A & _BV(UDRE0))); UDR0 = 0x0a; _delay_ms(6); /* 送信バッファの空きを待ってデータをセット CRLF送信*/
}
プログラムでは、123を入力すると234を返し、abを入力するとbcを返します。
PCへの出力では、同じ行に続けて文字列を表示したいことがあります。strcat()を使ってみましたがおかしな文字が入ったりして思うようにならなかったので、文字列を送るルーチン string_out () と crlfを送るルーチン cr() に分けました。このほうが使い勝手が良いと思います。
(2024.03.23) uart通信を割り込み無しで行い、ヘッダーファイルを作ってみました。
大変ズボラで、cpuは mega328、9600bps でしか使えません。
使える関数は inituart()、uart_write(s)、uart_read(s) の3つだけです。
inituart() は初期化関数です
uart_write(s) は文字列s[]をPCに送ります
uart_read(s) は文字列s[]をPCから読み取ります(cr または lf で)
割り込みをしていないのでPCから読み取りたいときにreadルーチンを使います。
プログラム一式のtxtファイルをここに置きます。メインルーチンはPCから送られた文字列の次の文字を返すだけです。
筆者メモ:E:\MC\_0MicroComp\AVR\gccPractice\uart_non_hfile
(2024.03.24)atmega8用のuart(割り込みのない)ヘッダーファイルを作りました。
レジスタの名前に"0"が無い、初期設定のレジスタが違う、ところが変わっています。機能はm328用と全く同じです。例題のmainは上記と変わりません。
プログラム一式のtxtファイルをここに置きます。
筆者メモ:E:\MC\_0MicroComp\AVR\gccPractice\uart_non_hfile _m8
(2024.03.31) uart送受信用のレシスタ設定の違いを書いてみました。 mega328、mega8、tiny2313用です。
書き方が統一されていません。 ここに置きます。
受信割り込み用のヘッダーファイルを作りました。mega328専用です。
ヘッダーファイルは、.hファイルに関数のプロトタイプと変数・定数定義と#includeファイルを書き、.cファイルに #include "x.h"と関数内容と初期設定の定義を書けばよいようです。ここにプログラムを置きます。
4 LCD表示 2024.03.11
LCD表示を試みました。すっと以前に実験したものですが復習としてあげておきます。
webにはヘッダーファイルがあるようですが、以前から使っていたものが簡単なようです。
.cファイルとヘッダーファイルを一緒にしたテキストファイルをここに置きます。
5 I2C接続 2024.03.15
arduinoではI2Cの接続も簡単なのですが、C言語の方ももう一度思い出そうとプロクラムファイルを探しました。
MAXIMの温度センサーを使ったものが見つかりましたので現在のgccでコンパイルしたところ、一部修正でコンパイルができました。そこでmega328のブレッドボードを組んで7セグledに表示したところ、無事思惑どおりになりました。
ここに、main.c とヘッダファイルを置きます。
なお、以前から居室の温度計はセンサーが普及品で信頼性が低いので更新したかったところですから新しく作ることにしました。LEDは昔ジャンクで買った文字高25mmの少し大きいものを使っています。cpuはmega48でコンパイルしたところ(makefileのcpuをmega48にして)正常に動きましたので温度計はmega48を使っています。
テスト中と完成間近の写真です。
.hファイルは10年以上も前にswNAK氏のプログラムから作ったものですが、見直してみると忘れていることも多く自分でも理解困難になります。他のプロクラムも探してもう一度わかりたいと思っています。
webにはヘッダファイルも多くあるようですが私には難しくてわかりません。ぼつぼつ考えましょう。
ファイルを探しているうちに-----------------------------------
もっと簡単に使えるヘッダーファイルが見つかりました。
I2CMsInit(); i2c_write_d(**); i2c_read_d(**);
の3つのコマンドだけで書き込み読み出しができるようです。過去には使っていますが、これから検証しましょう。
ファイルをここに置きます。
注意点を書いてみました。
********* i2c_mstr.h使用上の注意 ************************************************************************
i2c_mstr.h と i2c_mstr.c を使用プログラムフォルダに置くこと。
F_CPUはmakefileで決定する。プログラム最初に F_CPU 8000000UL と書くとmakefileに優先する。
.hファイルのはじめに #define TWBR_data xx を書くこと。F_CPU/1000000*1.25-2の値で、
8MHzなら8、12MHzなら13、16MHzなら18 を書く。
mainプログラムに I2CMsInit(); //マスター初期化 を書く、引数なし。
連続バイト書き込みは
データを d[] に入れて、
i2c_write_d(uint8_t adr,uint8_t hi_adr,uint8_t lo_adr,uint8_t n);
i2c_write_d(
uint8_t adr スレーブアドレス 8ビット偶数(bit0=0)
,uint8_t hi_adr スレーブ内部Hiアドレス(Hiがないときは 255 を指定する)
,uint8_t lo_adr スレーブ内部Loアドレス
,uint8_t n d[0](を含めて)からnバイトを書き込む(n=3ならn[0]〜n[2])
)
連続バイト読み出しは
i2c_read_d(uint8_t adr,uint8_t hi_adr,uint8_t lo_adr,uint8_t n);
i2c_read_d(
uint8_t adr スレーブアドレス 8ビット偶数(bit0=0)
,uint8_t hi_adr スレーブ内部Hiアドレス(Hiがないときは 255 を指定する)
,uint8_t lo_adr スレーブ内部Loアドレス
,uint8_t n スレーブ内部アドレスからnバイトをd[0]からへ読み出す(n=3ならn[0]〜n[2])
)
*細かな手続きは上記の2つのルーチンが行う。考えなくても良い。
注:SCL=100kHzで設定している。
d[35] は .hで設定しているのでmainでは設定しないこと。
スレーブの内部アドレスに highアドレスが無いときは255とする。(255のときはhi_adrは無視される)
一時は 0 とすることもあったが、eepromに hi_adr=0のものがあるので変更した。
*********************************************************************************************************
(2024.03.19) 手元にあったRTCモジュールDS3231をブレッドボードに接続しました。
目的はこのモジュールのI2Cをavr-gccで読み書きすることです。
webにライブラリを探したのですが、arduinoのものは多くあってもgcc用のものはほとんど無いようです。以前にデータシートを見ながら読み出したことがあるので試みました。
このチップは1バイトデータではなくて1バイトを4ビット2つに分けて記憶されているので、勘違いなどもあって少々時間がかかりました。
機能は、年月日時分秒の書き込みと読み出しです。入力・出力はシリアルを使っています。少しの時刻調整のために分の四捨五入機能をつけています。シリアル表示は秒が変わったときに表示するようにして、1秒に1回表示できるようにしました。
忘れていたc言語を少しずつ思い出しています。
rtcモジュールの写真です。
ファアイルをまとめたものです。
(2024.04.05) 以前から準備しながら手を付けずにいた温度計を作りました。
温度センサーは上のものと同じmaximのDS7505Sです。CPUは手持ちの関係でmega8を使いました。
上記の温度計のファイルをmega8でコンパイルしなおすだけで作動しました。
前の温度計を作った後で、より使いやすいI2Cヘッダーファイルが見つかりましたので今回はそれに書き換えてみたところ問題なく動きました。使う関数が
I2CMsInit(); i2c_write_d(**); i2c_read_d(**);
// 初期化、(uint8_t)配列ライト、(uint8_t)配列リード
の3つだけですからmainプログラムは極めて簡単になりました。 プロクラムです。
6 インクルードファイル 2024.03.20
今まで他の人のプログラムを真似て6 インクルードファイルを選んできました。
それで、どのような手続きが含まれているのか今でもわかりませんがこれから勉強しようと考えて、過去に使ったファイルを調べてみました。重複する、あるいは今では使えないものもあるかもしれません。
調べたリストを挙げてみます。
avr/io.h PORT設定、uint8_t
avr/interrupt.h 割り込みルーチン
avr/pgmspace.h PROGMEM
avr/wdt.h watchdog timer
avr/sleep.h
stdio.h stdin,stdout
stdlib.h itoa,fuse,rand,srand
string.h string
util/delay.h _delay_ms()
util/twi.h
math.h
time.h srand((unsigned int)time(NULL));
signal.h
stdbool.h fuse.h
interrupt.h ???
sig-avr.h
inttypes.h
iomacros.h
avr/iotn2313.h
io.h
io2313.h io.h に含まれるようだ
avr/delay.h _delay_ms()
avr gcc に思うこと 2024.04.03〜
F_CPUの指定:mainファイルの最初に #define F_CPU 8000000UL を書く方がよい。ULは必要。main()で使うことが多い。
_delay_ms()など。 makefileにも 8000000 を書くこと。このときULはつけない。
関数の引数なし: void main() とするとmakefileによってはwarnigになる。void main(void) と書く。
volatile変数の宣言:複数のファイルで使うときは volatile uint8_t i=0; はerrになる。0の初期設定はプログラム内ですること。volatile uint8_t i; とする。
シリアルなどに復帰改行CRLFを送るとき:'\n'の一文字を送ればよい。
includeファイル:main()と.hに同じincledeファイルがあっても問題はないようだ。
2 なんでもメモ (2016.)