W tym poście chciałbym przedstawić obsługę interfejsu I2C na przykładzie pamięci EEPROM oraz wyświetlacza 2x16.
[Źródło: https://somlabs.com/product/visionsom-6ull/]
Przykładowa obsługa I2C umieszczona na stronie producenta (przykład dotyczy pamięci EEPROM):
I2C:
W przypadku gdy sterownik jest uruchomiony można to sprawdzić przez wpisanie komendy:
- ls /sys/class/i2c-adapter
Domyślnie I2C-1 powinien być uruchomiony. W celu obsługi innego interfejsu należy przełożyć zmodyfikować device tree o następujące wpisy:
- &i2c1 {
- clock_frequency = <100000>;
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_i2c1>;
- status = "okay";
- };
- &i2c2 {
- clock_frequency = <100000>;
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_i2c2>;
- status = "okay";
- };
- &iomuxc {
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_hog_1>;
- imx6ul-evk {
- ...
- ...
- pinctrl_i2c1: i2c1grp {
- fsl,pins = <
- MX6UL_PAD_UART4_TX_DATA__I2C2_SCL 0x4001b8b0
- MX6UL_PAD_UART4_RX_DATA__I2C2_SDA 0x4001b8b0
- >;
- }
- pinctrl_i2c2: i2c2grp {
- fsl,pins = <
- MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
- MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
- >;
- }
- ...
- ...
- };
Aby umożliwić dostęp z poziomu interfejsu /dev należy zainstalować następujące biblioteki:
- apt install libi2c-dev i2c-tools
W celu ładowania modułu i2c-dev po starcie systemu należy go dopisać w pliku /etc/modules:
- sudo nano /etc/modules
Następnie w celu upewnienia się że instalacja przebiegła poprawnie można wyświetlić zawartość katalogu dev:
Do wyszukania adresu urządzenia można wykorzystać programu i2cdetect. Jako parametr podawany jest numer interfejsu I2C:
W moim przypadku adres I2C wynosi 0x27(sterownik PCF8574T). Inne dostępne sterowniki powinny się przedstawiać w następujący sposób:
- PCF8574 - 0x20;
- PCF8574T - 0x27;
- PCF8574A - 0x38;
- PCF8574AT - 0x3F;
Podłączenie I2C:
Podłączenie do linii I2C-1 jest dostępne z złącz RPI:
- Pin 1 - 3V3
- Pin 3 - SDA
- Pin 5 - SCL
- Pin 6 - GND
Poniżej fragment schematu udostępnionego przez producenta (link):
Te same piny wyprowadzone są również na złącze Arduino. Różnica polega na tym, że linie podłączone są tam przez konwerter poziomów logicznych z 5V na 3V3(TXS0108EPWR). Linie I2C na złączu żółtym 10 pinowym:
Obsługa wyświetlacza 2x16:
Do obsługi wyświetlacza wykorzystałem zmodyfikowaną bibliotekę pod układ STM32, która została umieszczona wcześniej na tym blogu.
Na samym początku uruchomienie I2C:
Na samym początku należy uruchomić linię dla odczytu i zapisu danych (O_RDWR). Otwarcie zwraca deskryptor który zostaje użyty do konfiguracji. W przypadku zwrócenia wartości ujemnej nastąpił błąd uruchamiania linii. Mógł być spowodowany błędną nazwą pliku do otwarcia lub niewystarczającymi uprawnieniami dostępu do pliku. Po udanym otwarciu linii należy rozpocząć komunikację z urządzeniem. Wykonywane jest przez przesłanie do podanego urządzenia adresu wraz z bitem zapisu/odczytu (bit 0 odczyt, bit 1 zapis).
Zapis danych:
Zamknięcie I2C:
Poniżej całe pliki z biblioteką oraz plik main:
Plik lcd2_16.c:
Plik lcd2_16.h:
Plik main.c:
W celu wykonania kompilacji programu należy w konsoli wpisać:
W przypadku korzystania z własnych bibliotek należy je uwzględnić w kompilacji.
Teraz wystarczy wywołać przygotowany program przez wprowadzenie w konsoli:
- #define I2C_FILE_NAME "/dev/i2c-1"
- #define I2C_DISP_ADDRESS 0x27
- int Open_I2C(const char* devPath, uint8_t devAddress) {
- int device;
- if((device = open(devPath, O_RDWR)) < 0 ) {
- return (-1);
- }
- if((ioctl(device, I2C_SLAVE, devAddress & 0x7F)) < 0 ) {
- return (-2);
- }
- return device;
- }
Na samym początku należy uruchomić linię dla odczytu i zapisu danych (O_RDWR). Otwarcie zwraca deskryptor który zostaje użyty do konfiguracji. W przypadku zwrócenia wartości ujemnej nastąpił błąd uruchamiania linii. Mógł być spowodowany błędną nazwą pliku do otwarcia lub niewystarczającymi uprawnieniami dostępu do pliku. Po udanym otwarciu linii należy rozpocząć komunikację z urządzeniem. Wykonywane jest przez przesłanie do podanego urządzenia adresu wraz z bitem zapisu/odczytu (bit 0 odczyt, bit 1 zapis).
Zapis danych:
- static uint8_t Write_I2C(int device, uint8_t value) {
- int status;
- uint8_t buffer[2] = {value, 0x00};
- if((status = write(device, buffer, 1))<0 ){
- printf("Write Error I2C %d\n", status);
- return 1;
- }
- usleep(2);
- return 0;
- }
Zamknięcie I2C:
- void Close_I2C(int device) {
- close(device);
- }
Poniżej całe pliki z biblioteką oraz plik main:
Plik lcd2_16.c:
- #include <stdio.h>
- #include <linux/i2c-dev.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <string.h>
- #include "lcd2_16.h"
- //---------------------------------------------------------------
- /* Global variables */
- uint8_t portlcd=0;
- //---------------------------------------------------------------
- /* Static functions declarations */
- static void Priv_Send_Halfbyte(int device, uint8_t c);
- static void Priv_Send_Byte(int device, uint8_t c, uint8_t mode);
- static uint8_t Write_I2C(int device, uint8_t value);
- //---------------------------------------------------------------
- /* I2C Interfejs functions */
- int Open_I2C(const char* devPath, uint8_t devAddress) {
- int device;
- if((device = open(devPath, O_RDWR)) < 0 ) {
- return (-1);
- }
- if((ioctl(device, I2C_SLAVE, devAddress & 0x7F)) < 0 ) {
- return (-2);
- }
- return device;
- }
- void Close_I2C(int device) {
- close(device);
- }
- static uint8_t Write_I2C(int device, uint8_t value) {
- int status;
- uint8_t buffer[2] = {value, 0x00};
- if((status = write(device, buffer, 1))<0 ){
- printf("Write Error I2C %d\n", status);
- return 1;
- }
- usleep(2);
- return 0;
- }
- //-------------------------------------------------------------
- static void Priv_Send_Halfbyte(int device, uint8_t c)
- {
- c<<=4;
- ENABLE_SET(device);
- usleep(50);
- Write_I2C(device, portlcd|c);
- ENABLE_RESET(device);
- usleep(50);
- }
- static void Priv_Send_Byte(int device, uint8_t c, uint8_t mode)
- {
- uint8_t hc=0;
- if (mode==0)
- {
- RS_RESET(device);
- }
- else
- {
- RS_SET(device);
- }
- hc=c>>4;
- Priv_Send_Halfbyte(device, hc);
- Priv_Send_Halfbyte(device, c);
- }
- //-------------------------------------------------------------
- void LCD_Initial(int device){
- for(uint8_t i=0;i<3;i++)
- {
- Priv_Send_Halfbyte(device, 0x03);
- usleep(45000);
- }
- //Wlaczenie trybu czterobitowego
- Priv_Send_Halfbyte(device, 0x02);
- usleep(100000);
- Priv_Send_Byte(device, HD44780_FUNCTION_SET | HD44780_FONT5_7 | HD44780_TWO$
- usleep(1000);
- Priv_Send_Byte(device, HD44780_DISPLAY_ONOFF | HD44780_DISPLAY_OFF,0);
- usleep(1000);
- Priv_Send_Byte(device, HD44780_ENTRY_MODE | HD44780_EM_SHIFT_CURSOR | HD447$
- usleep(1000);
- LED_SET(device);
- WRITE_SET(device);
- LCD_Clear(device);
- }
- void LCD_Clear(int device)
- {
- Priv_Send_Byte(device, 0x01,0);
- usleep(1000000);
- }
- void LCD_Send_Char(int device, char ch)
- {
- Priv_Send_Byte(device, ch,1);
- }
- void LCD_Send_String(int device, char* st
- {
- uint8_t i=0;
- while(st[i] != 0)
- {
- Priv_Send_Byte(device, st[i],1);
- i++;
- }
- }
- void LCD_Send_Str_Pos(int device, char* st, uint8_t x, uint8_t y)
- {
- LCD_Set_Position(device, x,y);
- LCD_Send_String(device, st);
- }
- void LCD_Set_Position(int device, uint8_t x, uint8_t y)
- {
- switch(y)
- {
- case 0:
- Priv_Send_Byte(device, x|0x80, 0);
- usleep(1000);
- break;
- case 1:
- Priv_Send_Byte(device, (0x40+x)|0x80, 0);
- usleep(1000);
- break;
- }
- }
Plik lcd2_16.h:
- #ifndef LCD2_16_H_
- #define LCD2_16_H_
- #include <stdio.h>
- #include <stdint.h>
- #define I2C_FILE_NAME "/dev/i2c-1"
- #define I2C_DISP_ADDRESS 0x27
- #define ENABLE_SET(x) Write_I2C(x, portlcd |= 0x04)
- #define ENABLE_RESET(x) Write_I2C(x, portlcd &=~ 0x04)
- #define RS_SET(x) Write_I2C(x, portlcd |= 0x01)
- #define RS_RESET(x) Write_I2C(x, portlcd &=~ 0x01)
- #define LED_SET(x) Write_I2C(x, portlcd |= 0x08)
- #define WRITE_SET(x) Write_I2C(x, portlcd &=~ 0x02)
- #define HD44780_CLEAR 0x01
- #define HD44780_HOME 0x02
- #define HD44780_ENTRY_MODE 0x04
- #define HD44780_EM_SHIFT_CURSOR 0
- #define HD44780_EM_SHIFT_DISPLAY 1
- #define HD44780_EM_DECREMENT 0
- #define HD44780_EM_INCREMENT 2
- #define HD44780_DISPLAY_ONOFF 0x08
- #define HD44780_DISPLAY_OFF 0
- #define HD44780_DISPLAY_ON 4
- #define HD44780_CURSOR_OFF 0
- #define HD44780_CURSOR_ON 2
- #define HD44780_CURSOR_NOBLINK 0
- #define HD44780_CURSOR_BLINK 1
- #define HD44780_DISPLAY_CURSOR_SHIFT 0x10
- #define HD44780_SHIFT_CURSOR 0
- #define HD44780_SHIFT_DISPLAY 8
- #define HD44780_SHIFT_LEFT 0
- #define HD44780_SHIFT_RIGHT 4
- #define HD44780_FUNCTION_SET 0x20
- #define HD44780_FONT5_7 0
- #define HD44780_FONT5_10 4
- #define HD44780_ONE_LINE 0
- #define HD44780_TWO_LINE 8
- #define HD44780_4_BIT 0
- #define HD44780_8_BIT 16
- #define HD44780_CGRAM_SET 0x40
- #define HD44780_DDRAM_SET 0x80
- /* I2C */
- int Open_I2C(const char* devPath, uint8_t devAddress);
- void Close_I2C(int device);
- /* DISPLAY */
- void LCD_Initial(int device);
- void LCD_Clear(int device);
- void LCD_Send_Char(int device, char ch);
- void LCD_Send_String (int device, char* st);
- void LCD_Set_Position(int device, uint8_t x, uint8_t y);
- void LCD_Send_Str_Pos(int device, char* st, uint8_t x, uint8_t y);
- #endif /* LCD2_16_H_ */
Plik main.c:
- #include <stdio.h>
- #include <string.h>
- #include <linux/fb.h>
- #include "lcd2_16.h"
- int main(){
- int i2c_dev = Open_I2C(I2C_FILE_NAME, I2C_DISP_ADDRESS);
- if(i2c_dev <0){
- printf("Blad I2C: %d\n", i2c_dev);
- return 1;
- }
- else{
- printf("I2C dziala: %d\n", i2c_dev);
- }
- LCD_Initial(i2c_dev);
- LCD_Send_Str_Pos(i2c_dev, "linia1",0,0);
- LCD_Send_Str_Pos(i2c_dev, "linia2",0,1);
- Close_I2C(i2c_dev);
- return 0;
- }
W celu wykonania kompilacji programu należy w konsoli wpisać:
- gcc lcd2_16.c main.c -o main
W przypadku korzystania z własnych bibliotek należy je uwzględnić w kompilacji.
Teraz wystarczy wywołać przygotowany program przez wprowadzenie w konsoli:
- ./main