C言語でのデバイス制御方法をマスターするための完全ガイド

C言語は、ハードウェアと直接対話できる低レベルのプログラミング言語として、デバイス制御の分野で広く使用されています。本ガイドでは、C言語を用いたデバイス制御の基本から応用までを詳細に解説します。デバイスドライバの作成や通信プロトコルの実装を通じて、実際のデバイス制御技術を習得し、現場で役立つスキルを身につけましょう。

目次

デバイス制御の基本概念

デバイス制御は、コンピュータが外部デバイス(ハードウェア)と通信し、操作するための技術です。C言語は、その効率性とハードウェアとの親和性から、デバイス制御の分野で特に重要な役割を果たします。このセクションでは、デバイス制御の基本概念を理解し、C言語がどのようにこれをサポートするかを説明します。

デバイス制御とは

デバイス制御は、コンピュータシステムが周辺機器(プリンタ、ハードディスク、ディスプレイなど)を管理し操作するためのプロセスです。この制御には、データの送受信やデバイスの動作状態の監視が含まれます。

C言語の役割

C言語は、低レベルのメモリ操作とハードウェアアクセスが可能であるため、デバイス制御において広く用いられます。直接的なハードウェア操作が可能なC言語は、高性能で効率的なデバイス制御を実現します。

メモリ操作

C言語では、ポインタを使用してメモリに直接アクセスできます。これにより、デバイスのレジスタに直接読み書きすることが可能です。

ハードウェアインターフェース

C言語は、特定のハードウェアインターフェースと通信するためのライブラリやAPIを利用して、デバイス制御を効率的に行うことができます。

デバイスドライバとは

デバイスドライバは、オペレーティングシステムがハードウェアと通信するためのソフトウェアコンポーネントです。C言語でデバイスドライバを開発することで、特定のデバイスを効率的に制御・操作することが可能になります。

デバイスドライバの概要

デバイスドライバは、ハードウェアとオペレーティングシステムの間でデータのやり取りを行うためのソフトウェアです。ドライバはハードウェアの動作を抽象化し、アプリケーションプログラムがハードウェアを直接操作することなく利用できるようにします。

デバイスドライバの役割

デバイスドライバの主な役割は、以下の通りです:

  • ハードウェアの抽象化:ハードウェアの詳細を隠し、標準化されたインターフェースを提供します。
  • 通信の管理:デバイスとオペレーティングシステム間のデータ通信を管理します。
  • エラー処理:ハードウェアのエラーを検出し、適切に処理します。

C言語でのデバイスドライバ開発

C言語は、効率的なメモリ操作とハードウェアアクセスが可能なため、デバイスドライバ開発に最適です。以下に、C言語でデバイスドライバを開発する際の基本的な手法を示します:

ハードウェアレジスタの操作

デバイスの制御は、主にハードウェアレジスタへの読み書き操作を通じて行います。C言語では、ポインタを用いてこれを実現します。

#define DEVICE_REGISTER_ADDRESS 0x1000
volatile unsigned int *device_register = (unsigned int *)DEVICE_REGISTER_ADDRESS;

// レジスタへの書き込み
*device_register = 0x01;

// レジスタからの読み出し
unsigned int status = *device_register;

割り込み処理

デバイスドライバは、デバイスからの割り込みを処理する必要があります。割り込みハンドラは通常、特定の割り込みベクタに関連付けられた関数として実装されます。

void device_interrupt_handler(void) {
    // 割り込み処理コード
}

通信プロトコルの基礎

デバイス間の通信において、プロトコルは重要な役割を果たします。通信プロトコルとは、データの送受信方法を定義する規則の集合です。このセクションでは、基本的な通信プロトコルとその重要性について説明します。

通信プロトコルの重要性

通信プロトコルは、異なるデバイスがデータを交換するための共通のルールを提供します。これにより、データの正確な送受信が保証され、デバイス間の互換性が確保されます。

代表的な通信プロトコル

デバイス制御においてよく使用される通信プロトコルには、UART、SPI、I2Cなどがあります。それぞれのプロトコルには独自の特徴と利点があります。

UART(Universal Asynchronous Receiver/Transmitter)

UARTは、シリアル通信プロトコルの一つで、1対1の通信に適しています。非同期通信を使用し、スタートビットとストップビットでデータの送受信を制御します。

SPI(Serial Peripheral Interface)

SPIは、高速なデータ転送を実現するための同期シリアル通信プロトコルです。マスターとスレーブの構成で、多数のデバイスを制御することができます。

I2C(Inter-Integrated Circuit)

I2Cは、複数のデバイス間でデータをやり取りするための同期シリアル通信プロトコルです。2本の信号線(SDAとSCL)を使用し、多数のデバイスをバス上に接続できます。

プロトコルの選択

プロトコルの選択は、デバイスの特性やアプリケーションの要件に基づいて行います。通信速度、デバイス数、データの信頼性などを考慮して、最適なプロトコルを選択します。

メモリマップドI/Oの基本

メモリマップドI/Oは、デバイスとメモリが同じアドレス空間を共有する方式で、デバイス制御の基本技術の一つです。このセクションでは、メモリマップドI/Oの仕組みとC言語での実装方法について説明します。

メモリマップドI/Oとは

メモリマップドI/Oでは、デバイスのレジスタがシステムメモリのアドレス空間にマッピングされます。これにより、デバイスをメモリのように読み書きできるようになります。CPUは通常のメモリアクセス命令を使用してデバイスと通信します。

メモリマップドI/Oの利点

  • 高速なアクセス:メモリアクセスと同様の方法でデバイスにアクセスできるため、高速なデータ転送が可能です。
  • 統一されたアドレッシング:メモリ空間を通じてデバイスを管理するため、プログラムの簡素化が図れます。

C言語でのメモリマップドI/Oの実装

C言語では、ポインタを使用してメモリマップドI/Oのアドレスにアクセスします。以下に、基本的なメモリマップドI/Oの実装例を示します。

デバイスレジスタへのアクセス

まず、デバイスレジスタのアドレスを定義し、それをポインタで参照します。その後、ポインタを通じてレジスタにデータを書き込み、読み出します。

#define DEVICE_REGISTER_ADDRESS 0x2000
volatile unsigned int *device_register = (unsigned int *)DEVICE_REGISTER_ADDRESS;

// レジスタへの書き込み
*device_register = 0x1234;

// レジスタからの読み出し
unsigned int data = *device_register;

実際のデバイス制御例

次に、具体的なデバイス制御の例として、LEDを点灯・消灯するプログラムを示します。

#define LED_CONTROL_REGISTER 0x2000
volatile unsigned int *led_control = (unsigned int *)LED_CONTROL_REGISTER;

// LEDを点灯する
*led_control = 0x1;

// LEDを消灯する
*led_control = 0x0;

注意点

メモリマップドI/Oを使用する際には、デバイスのアドレス空間が重ならないように注意する必要があります。また、デバイスの特性に応じた適切な同期処理も重要です。

ポートI/Oの基本

ポートI/Oは、特定の入出力ポートを使用してデバイスと通信する方式です。このセクションでは、ポートI/Oの基本概念とC言語での制御方法について説明します。

ポートI/Oとは

ポートI/Oは、入出力ポートを介してデバイスとデータの送受信を行います。各ポートには特定のアドレスが割り当てられており、CPUはこれらのポートを通じてデバイスと通信します。

ポートI/Oの利点

  • シンプルなアドレッシング:デバイスごとに専用のI/Oポートが割り当てられるため、シンプルなアドレッシングが可能です。
  • ハードウェア制御の容易さ:直接ポートを操作するため、ハードウェアの制御が容易です。

C言語でのポートI/Oの実装

C言語でポートI/Oを操作する際には、特定のアセンブリ命令を使用することが一般的です。以下に、基本的なポートI/Oの実装例を示します。

ポートへの書き込み

特定のポートアドレスにデータを書き込む場合、out命令を使用します。

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

#define PORT_ADDRESS 0x300

// ポートにデータを書き込む関数
void write_port(unsigned char data) {
    outp(PORT_ADDRESS, data);
}

ポートからの読み出し

特定のポートアドレスからデータを読み出す場合、in命令を使用します。

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

#define PORT_ADDRESS 0x300

// ポートからデータを読み出す関数
unsigned char read_port() {
    return inp(PORT_ADDRESS);
}

具体的な制御例

ここでは、簡単なデバイス制御の例として、スイッチの状態を読み取り、LEDを点灯・消灯するプログラムを示します。

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

#define SWITCH_PORT 0x300
#define LED_PORT 0x301

void control_device() {
    unsigned char switch_state = read_port();

    if (switch_state == 1) {
        // スイッチが押された場合、LEDを点灯
        write_port(1);
    } else {
        // スイッチが押されていない場合、LEDを消灯
        write_port(0);
    }
}

UART通信の実装

UART(Universal Asynchronous Receiver/Transmitter)は、シリアル通信の一種で、デバイス間で非同期にデータを送受信します。このセクションでは、UART通信の基本からC言語での実装方法までを具体的に解説します。

UART通信の基本

UART通信は、スタートビット、データビット、パリティビット、ストップビットで構成されるフレームを使用してデータを送受信します。これにより、送信側と受信側はクロック同期なしで通信を行うことができます。

UARTの構成要素

  • スタートビット:通信の開始を示すビット
  • データビット:実際に送信されるデータ(通常8ビット)
  • パリティビット:データの誤り検出用(オプション)
  • ストップビット:通信の終了を示すビット

UART通信の初期化

UART通信を開始するためには、通信速度(ボーレート)、データビット長、パリティ、およびストップビットを設定する必要があります。以下に、C言語を用いたUARTの初期化コードを示します。

#include <avr/io.h>

void uart_init(unsigned int baudrate) {
    unsigned int ubrr = F_CPU/16/baudrate-1;
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    UCSR0B = (1<<RXEN0) | (1<<TXEN0); // 受信と送信を有効化
    UCSR0C = (1<<USBS0) | (3<<UCSZ00); // ストップビット1つ、データビット8つ
}

データの送信

UARTを用いてデータを送信する方法を以下に示します。

void uart_transmit(unsigned char data) {
    while (!(UCSR0A & (1<<UDRE0))); // 送信バッファが空になるのを待つ
    UDR0 = data; // データを送信バッファに書き込む
}

データの受信

UARTを用いてデータを受信する方法を以下に示します。

unsigned char uart_receive(void) {
    while (!(UCSR0A & (1<<RXC0))); // データが受信されるのを待つ
    return UDR0; // 受信データを返す
}

UART通信の例

以下に、UART通信を使用してデバイス間でデータを送受信するプログラム例を示します。ここでは、PCから送信されたデータを受信し、そのデータを再びPCに送信します。

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    uart_init(9600); // ボーレート9600でUARTを初期化

    while (1) {
        unsigned char received_data = uart_receive(); // データを受信
        uart_transmit(received_data); // 受信したデータを送信
    }

    return 0;
}

このプログラムは、エコーバック(受信したデータをそのまま送り返す)機能を実装しています。これにより、UART通信の基本的な動作を確認できます。

SPI通信の実装

SPI(Serial Peripheral Interface)は、高速な同期シリアル通信プロトコルであり、マスターとスレーブの構成で多くのデバイスを制御できます。このセクションでは、SPI通信の基本からC言語での実装方法について詳しく説明します。

SPI通信の基本

SPI通信は、クロック信号を利用してデータを同期的に転送します。通信には4本の線(MISO、MOSI、SCK、SS)が使用されます。

SPIの構成要素

  • MISO(Master In Slave Out):スレーブからマスターへのデータライン
  • MOSI(Master Out Slave In):マスターからスレーブへのデータライン
  • SCK(Serial Clock):クロック信号ライン
  • SS(Slave Select):スレーブ選択信号ライン

SPIの初期化

SPI通信を開始するためには、マスター/スレーブの設定、クロックポラリティ、クロック位相、データ順序などを設定する必要があります。以下に、C言語を用いたSPIの初期化コードを示します。

#include <avr/io.h>

void spi_init_master(void) {
    // MOSI, SCKを出力に設定
    DDRB |= (1<<PB3) | (1<<PB5);
    // MISOを入力に設定
    DDRB &= ~(1<<PB4);
    // SPIを有効化し、マスターに設定
    SPCR = (1<<SPE) | (1<<MSTR);
}

void spi_init_slave(void) {
    // MISOを出力に設定
    DDRB |= (1<<PB4);
    // MOSI, SCKを入力に設定
    DDRB &= ~(1<<PB3) & ~(1<<PB5);
    // SPIを有効化
    SPCR = (1<<SPE);
}

データの送受信

SPIを用いてデータを送受信する方法を以下に示します。

データ送信(マスター側)

マスター側でデータを送信する関数を以下に示します。

void spi_transmit(unsigned char data) {
    SPDR = data; // データを送信バッファに書き込む
    while(!(SPSR & (1<<SPIF))); // 送信完了を待つ
}

データ受信(スレーブ側)

スレーブ側でデータを受信する関数を以下に示します。

unsigned char spi_receive(void) {
    while(!(SPSR & (1<<SPIF))); // 受信完了を待つ
    return SPDR; // 受信データを返す
}

SPI通信の例

以下に、マスターとスレーブ間でデータを送受信するプログラム例を示します。マスターがスレーブにデータを送信し、スレーブが受信したデータを確認します。

マスター側プログラム

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    spi_init_master(); // SPIマスターを初期化
    while (1) {
        spi_transmit(0xAA); // データ0xAAを送信
        _delay_ms(1000); // 1秒待機
    }
    return 0;
}

スレーブ側プログラム

#include <avr/io.h>

int main(void) {
    spi_init_slave(); // SPIスレーブを初期化
    unsigned char received_data;
    while (1) {
        received_data = spi_receive(); // データを受信
        // 受信データを処理(例:LEDを点灯)
        if (received_data == 0xAA) {
            // LED点灯コードなど
        }
    }
    return 0;
}

この例では、マスターが0xAAのデータをスレーブに送信し、スレーブがそれを受信して特定の動作(例えばLEDの点灯)を行うことを示しています。

I2C通信の実装

I2C(Inter-Integrated Circuit)は、2本の信号線(SDAとSCL)を使用してデバイス間の通信を行う同期シリアル通信プロトコルです。このセクションでは、I2C通信の基本からC言語での実装方法について詳しく説明します。

I2C通信の基本

I2Cは、マスターと複数のスレーブデバイス間でデータをやり取りするために使用されます。マスターは通信の開始、停止、アドレス指定、データの送信および受信を管理します。

I2Cの構成要素

  • SDA(Serial Data Line):データ転送用の線
  • SCL(Serial Clock Line):クロック信号用の線
  • アドレス:各スレーブデバイスに割り当てられる一意のアドレス

I2Cの初期化

I2C通信を開始するためには、通信速度、デバイスアドレス、およびその他の設定を行う必要があります。以下に、C言語を用いたI2Cの初期化コードを示します。

#include <avr/io.h>

void i2c_init(void) {
    // クロック速度を設定(例:100kHz)
    TWSR = 0x00;
    TWBR = ((F_CPU/100000UL)-16)/2;
    // I2Cを有効化
    TWCR = (1<<TWEN);
}

データの送信

I2Cを用いてデータを送信する方法を以下に示します。

void i2c_start(void) {
    TWCR = (1<<TWSTA) | (1<<TWEN) | (1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}

void i2c_stop(void) {
    TWCR = (1<<TWSTO) | (1<<TWEN) | (1<<TWINT);
}

void i2c_write(unsigned char data) {
    TWDR = data;
    TWCR = (1<<TWEN) | (1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}

データの受信

I2Cを用いてデータを受信する方法を以下に示します。

unsigned char i2c_read_ack(void) {
    TWCR = (1<<TWEN) | (1<<TWINT) | (1<<TWEA);
    while (!(TWCR & (1<<TWINT)));
    return TWDR;
}

unsigned char i2c_read_nack(void) {
    TWCR = (1<<TWEN) | (1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
    return TWDR;
}

I2C通信の例

以下に、I2C通信を使用してEEPROMにデータを書き込み、読み出すプログラム例を示します。

EEPROMへの書き込み

#include <avr/io.h>
#include <util/delay.h>

void eeprom_write(unsigned char device_addr, unsigned char mem_addr, unsigned char data) {
    i2c_start();
    i2c_write(device_addr);
    i2c_write(mem_addr);
    i2c_write(data);
    i2c_stop();
    _delay_ms(10); // 書き込み完了まで待機
}

EEPROMからの読み出し

unsigned char eeprom_read(unsigned char device_addr, unsigned char mem_addr) {
    unsigned char data;
    i2c_start();
    i2c_write(device_addr);
    i2c_write(mem_addr);
    i2c_start();
    i2c_write(device_addr | 0x01);
    data = i2c_read_nack();
    i2c_stop();
    return data;
}

この例では、EEPROMにデータを書き込み、読み出す基本的な操作を示しています。マスターはデバイスアドレスを指定して通信を開始し、データの送受信を行います。

実際のデバイス制御の例

このセクションでは、具体的なデバイス制御の例をいくつか取り上げ、C言語を用いた実装方法を詳細に説明します。ここでは、センサーデータの取得やモーターの制御など、実際の応用例を通じてデバイス制御の理解を深めます。

温度センサーのデータ取得

温度センサー(例:LM35)からデータを取得し、その値を表示するプログラム例を示します。温度センサーはアナログ信号を出力するため、ADC(アナログ-デジタルコンバータ)を使用してデジタルデータに変換します。

ADCの初期化

#include <avr/io.h>

void adc_init(void) {
    // AVCCを基準電圧とし、ADCを有効化
    ADMUX = (1<<REFS0);
    ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // プリスケーラを128に設定
}

ADCからのデータ読み出し

unsigned int adc_read(unsigned char ch) {
    ch &= 0b00000111; // チャンネル0〜7をマスク
    ADMUX = (ADMUX & 0xF8) | ch; // チャンネルを選択
    ADCSRA |= (1<<ADSC); // 変換開始
    while (ADCSRA & (1<<ADSC)); // 変換終了を待つ
    return ADC;
}

温度データの取得と表示

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

void uart_init(unsigned int baudrate);

void uart_transmit(unsigned char data);

int main(void) {
    adc_init(); // ADCを初期化
    uart_init(9600); // UARTを初期化

    char buffer[10];
    unsigned int adc_value;
    float temperature;

    while (1) {
        adc_value = adc_read(0); // ADCチャンネル0からデータを読み取る
        temperature = adc_value * (5.0 / 1024.0) * 100.0; // 電圧を温度に変換
        sprintf(buffer, "%.2f\n", temperature);
        uart_transmit(buffer); // 温度をUARTで送信
        _delay_ms(1000); // 1秒待機
    }
    return 0;
}

モーターの制御

次に、DCモーターを制御する例を示します。PWM(パルス幅変調)を使用してモーターの速度を調整します。

PWMの初期化

#include <avr/io.h>

void pwm_init(void) {
    // PWMピンを出力に設定
    DDRB |= (1<<PB1);
    // PWMを高速モードに設定し、非反転モードを有効化
    TCCR1A = (1<<COM1A1) | (1<<WGM11) | (1<<WGM10);
    TCCR1B = (1<<WGM12) | (1<<CS11); // プリスケーラを8に設定
}

モーターの速度制御

void set_motor_speed(uint8_t speed) {
    OCR1A = speed; // デューティサイクルを設定
}

int main(void) {
    pwm_init(); // PWMを初期化

    while (1) {
        set_motor_speed(128); // モーター速度を50%に設定
        _delay_ms(5000); // 5秒間動作
        set_motor_speed(0); // モーターを停止
        _delay_ms(5000); // 5秒間待機
    }
    return 0;
}

このプログラムでは、PWMを使用してモーターの速度を制御し、5秒間動作させてから停止します。PWMのデューティサイクルを調整することで、モーターの速度を変化させることができます。

デバイス制御の応用例

デバイス制御の技術は、単純な制御から高度なシステムまで幅広く応用できます。このセクションでは、複雑なシステムでの応用例をいくつか紹介し、C言語を用いた実装方法について説明します。

ロボットアームの制御

ロボットアームの制御は、複数のモーターやセンサーを協調させて動作させる高度な応用例です。ここでは、PWMを使用してロボットアームの関節を制御する方法を示します。

ロボットアームの初期化

#include <avr/io.h>

void pwm_init(void) {
    // 各関節のPWMピンを出力に設定
    DDRB |= (1<<PB1) | (1<<PB2) | (1<<PB3);
    // PWMを高速モードに設定し、非反転モードを有効化
    TCCR1A = (1<<COM1A1) | (1<<COM1B1) | (1<<COM1C1) | (1<<WGM11) | (1<<WGM10);
    TCCR1B = (1<<WGM12) | (1<<CS11); // プリスケーラを8に設定
}

関節の角度制御

void set_joint_angle(uint8_t joint, uint8_t angle) {
    switch (joint) {
        case 1:
            OCR1A = angle;
            break;
        case 2:
            OCR1B = angle;
            break;
        case 3:
            OCR1C = angle;
            break;
    }
}

int main(void) {
    pwm_init(); // PWMを初期化

    while (1) {
        set_joint_angle(1, 128); // 関節1を90度に設定
        set_joint_angle(2, 64);  // 関節2を45度に設定
        set_joint_angle(3, 192); // 関節3を135度に設定
        _delay_ms(1000); // 1秒待機
    }
    return 0;
}

このプログラムでは、3つの関節の角度を個別に制御し、ロボットアームを特定の位置に移動させます。PWMのデューティサイクルを変更することで、関節の角度を調整します。

自律移動ロボットの制御

自律移動ロボットは、センサーを用いて環境を認識し、自律的に動作するシステムです。ここでは、超音波センサーとモーターを使用して、障害物を避けながら移動するロボットの例を示します。

超音波センサーの初期化

#include <avr/io.h>
#include <util/delay.h>

void ultrasonic_init(void) {
    DDRD |= (1<<PD0); // トリガピンを出力に設定
    DDRD &= ~(1<<PD1); // エコーピンを入力に設定
}

距離の測定

unsigned int measure_distance(void) {
    // トリガ信号を送信
    PORTD |= (1<<PD0);
    _delay_us(10);
    PORTD &= ~(1<<PD0);

    // エコー信号の受信
    while (!(PIND & (1<<PD1))); // ハイになるのを待つ
    TCNT1 = 0;
    TCCR1B |= (1<<CS10); // タイマー開始

    while (PIND & (1<<PD1)); // ローになるのを待つ
    TCCR1B &= ~(1<<CS10); // タイマー停止

    return TCNT1;
}

ロボットの動作制御

#include <avr/io.h>
#include <util/delay.h>

void motor_init(void) {
    // モーターピンを出力に設定
    DDRB |= (1<<PB1) | (1<<PB2);
}

void set_motor_speed(uint8_t left_speed, uint8_t right_speed) {
    OCR1A = left_speed;
    OCR1B = right_speed;
}

int main(void) {
    ultrasonic_init(); // 超音波センサーを初期化
    pwm_init(); // PWMを初期化
    motor_init(); // モーターを初期化

    unsigned int distance;

    while (1) {
        distance = measure_distance();
        if (distance < 100) {
            // 障害物が近い場合、方向転換
            set_motor_speed(0, 128); // 左モーター停止、右モーター前進
        } else {
            // 障害物がない場合、前進
            set_motor_speed(128, 128); // 両モーター前進
        }
        _delay_ms(100);
    }
    return 0;
}

このプログラムでは、超音波センサーで距離を測定し、障害物が検出された場合に方向を変えて回避します。PWMを使用してモーターの速度を制御し、ロボットの動作を調整します。

演習問題と解答例

このセクションでは、デバイス制御に関する学んだ内容を確認するための演習問題を提供し、その解答例を示します。各演習問題は、具体的な実装方法を理解し、実際にプログラムを作成する能力を養うことを目的としています。

演習問題1: 温度センサーのデータ取得と表示

温度センサー(LM35)を使用して温度データを取得し、その値をシリアルモニターに表示するプログラムを作成してください。ADCを使用してアナログ信号をデジタル信号に変換し、UARTを使用してデータを送信します。

解答例

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

void adc_init(void) {
    ADMUX = (1<<REFS0);
    ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
}

unsigned int adc_read(unsigned char ch) {
    ch &= 0b00000111;
    ADMUX = (ADMUX & 0xF8) | ch;
    ADCSRA |= (1<<ADSC);
    while (ADCSRA & (1<<ADSC));
    return ADC;
}

void uart_init(unsigned int baudrate) {
    unsigned int ubrr = F_CPU/16/baudrate-1;
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    UCSR0B = (1<<RXEN0) | (1<<TXEN0);
    UCSR0C = (1<<USBS0) | (3<<UCSZ00);
}

void uart_transmit(unsigned char data) {
    while (!(UCSR0A & (1<<UDRE0)));
    UDR0 = data;
}

void uart_transmit_string(char* str) {
    while (*str) {
        uart_transmit(*str++);
    }
}

int main(void) {
    adc_init();
    uart_init(9600);

    char buffer[10];
    unsigned int adc_value;
    float temperature;

    while (1) {
        adc_value = adc_read(0);
        temperature = adc_value * (5.0 / 1024.0) * 100.0;
        sprintf(buffer, "%.2f\n", temperature);
        uart_transmit_string(buffer);
        _delay_ms(1000);
    }
    return 0;
}

演習問題2: モーターの速度制御

PWMを使用してDCモーターの速度を制御するプログラムを作成してください。モーターの速度を段階的に増減させ、動作を確認します。

解答例

#include <avr/io.h>
#include <util/delay.h>

void pwm_init(void) {
    DDRB |= (1<<PB1);
    TCCR1A = (1<<COM1A1) | (1<<WGM11) | (1<<WGM10);
    TCCR1B = (1<<WGM12) | (1<<CS11);
}

void set_motor_speed(uint8_t speed) {
    OCR1A = speed;
}

int main(void) {
    pwm_init();

    while (1) {
        for (uint8_t speed = 0; speed < 255; speed += 10) {
            set_motor_speed(speed);
            _delay_ms(100);
        }
        for (uint8_t speed = 255; speed > 0; speed -= 10) {
            set_motor_speed(speed);
            _delay_ms(100);
        }
    }
    return 0;
}

演習問題3: I2C通信によるEEPROMへのデータ書き込みと読み出し

I2C通信を使用してEEPROMにデータを書き込み、そのデータを読み出すプログラムを作成してください。データの書き込みと読み出しが正しく行われることを確認します。

解答例

#include <avr/io.h>
#include <util/delay.h>

void i2c_init(void) {
    TWSR = 0x00;
    TWBR = ((F_CPU/100000UL)-16)/2;
    TWCR = (1<<TWEN);
}

void i2c_start(void) {
    TWCR = (1<<TWSTA) | (1<<TWEN) | (1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}

void i2c_stop(void) {
    TWCR = (1<<TWSTO) | (1<<TWEN) | (1<<TWINT);
}

void i2c_write(unsigned char data) {
    TWDR = data;
    TWCR = (1<<TWEN) | (1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}

unsigned char i2c_read_ack(void) {
    TWCR = (1<<TWEN) | (1<<TWINT) | (1<<TWEA);
    while (!(TWCR & (1<<TWINT)));
    return TWDR;
}

unsigned char i2c_read_nack(void) {
    TWCR = (1<<TWEN) | (1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
    return TWDR;
}

void eeprom_write(unsigned char device_addr, unsigned char mem_addr, unsigned char data) {
    i2c_start();
    i2c_write(device_addr);
    i2c_write(mem_addr);
    i2c_write(data);
    i2c_stop();
    _delay_ms(10);
}

unsigned char eeprom_read(unsigned char device_addr, unsigned char mem_addr) {
    unsigned char data;
    i2c_start();
    i2c_write(device_addr);
    i2c_write(mem_addr);
    i2c_start();
    i2c_write(device_addr | 0x01);
    data = i2c_read_nack();
    i2c_stop();
    return data;
}

int main(void) {
    i2c_init();

    eeprom_write(0xA0, 0x00, 0x55); // データ0x55を書き込み
    _delay_ms(10);
    unsigned char data = eeprom_read(0xA0, 0x00); // データを読み出し

    while (1) {
        // 取得したデータを使用して何かを行う(例:LED点灯)
    }
    return 0;
}

まとめ

C言語を用いたデバイス制御の基本から応用までを学びました。デバイスドライバの作成、各種通信プロトコル(UART、SPI、I2C)によるデータ送受信、実際のデバイス制御例を通じて、実践的な知識を習得できたことと思います。これらのスキルを活用し、より複雑なシステムやプロジェクトにも挑戦してみてください。

コメント

コメントする

目次