czwartek, 5 marca 2020

Som Labs - IMx6 ULL - Debian - Obsługa I2C na przykładzie wyświetlacza 2x16

W tym poście chciałbym przedstawić obsługę interfejsu I2C na przykładzie pamięci EEPROM oraz wyświetlacza 2x16.

Znalezione obrazy dla zapytania som labs 6ull
[Ź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:

  1. 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:

  1. &i2c1 {
  2.         clock_frequency = <100000>;
  3.         pinctrl-names = "default";
  4.         pinctrl-0 = <&pinctrl_i2c1>;
  5.         status = "okay";
  6. };
  7. &i2c2 {
  8.         clock_frequency = <100000>;
  9.         pinctrl-names = "default";
  10.         pinctrl-0 = <&pinctrl_i2c2>;
  11.         status = "okay";
  12. };
  13. &iomuxc {
  14.         pinctrl-names = "default";
  15.         pinctrl-0 = <&pinctrl_hog_1>;
  16.         imx6ul-evk {
  17.                 ...
  18.                 ...
  19.                pinctrl_i2c1: i2c1grp {
  20.                         fsl,pins = <
  21.                                 MX6UL_PAD_UART4_TX_DATA__I2C2_SCL 0x4001b8b0
  22.                                 MX6UL_PAD_UART4_RX_DATA__I2C2_SDA 0x4001b8b0
  23.                         >;
  24.                 }
  25.                
  26.                 pinctrl_i2c2: i2c2grp {
  27.                         fsl,pins = <
  28.                                 MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
  29.                                 MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
  30.                         >;
  31.                 }
  32.                
  33.                 ...
  34.                 ...
  35.                
  36. };

Aby umożliwić dostęp z poziomu interfejsu /dev należy zainstalować następujące biblioteki:

  1. apt install libi2c-dev i2c-tools

W celu ładowania modułu i2c-dev po starcie systemu należy go dopisać w pliku /etc/modules:

  1. 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;

W przypadku gdy zamiast podanego adresu będzie wyświetlony napis UU. Oznacza to, że dane urządzenie jest wykorzystywane przez jakiś sterownik.

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:

  1. #define I2C_FILE_NAME "/dev/i2c-1"
  2. #define I2C_DISP_ADDRESS 0x27
  3. int Open_I2C(const char* devPath, uint8_t devAddress) {
  4.     int device;
  5.     if((device = open(devPath, O_RDWR)) < 0 ) {
  6.        return (-1);
  7.     }
  8.     if((ioctl(device, I2C_SLAVE, devAddress & 0x7F)) < 0 ) {
  9.        return (-2);
  10.     }
  11.     return device;
  12. }

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:

  1. static uint8_t Write_I2C(int device, uint8_t value) {
  2.     int status;
  3.     uint8_t buffer[2] = {value, 0x00};
  4.     if((status = write(device, buffer, 1))<0 ){
  5.         printf("Write Error I2C %d\n", status);
  6.         return 1;
  7.     }
  8.     usleep(2);
  9.     return 0;
  10. }

Zamknięcie I2C:

  1. void Close_I2C(int device) {
  2.     close(device);
  3. }

Poniżej całe pliki z biblioteką oraz plik main:

Plik lcd2_16.c:

  1. #include <stdio.h>
  2. #include <linux/i2c-dev.h>
  3. #include <fcntl.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <sys/ioctl.h>
  7. #include <string.h>
  8. #include "lcd2_16.h"
  9. //---------------------------------------------------------------
  10. /* Global variables */
  11. uint8_t portlcd=0;
  12. //---------------------------------------------------------------
  13. /* Static functions declarations */
  14. static void Priv_Send_Halfbyte(int device, uint8_t c);
  15. static void Priv_Send_Byte(int device, uint8_t c, uint8_t mode);
  16. static uint8_t Write_I2C(int device, uint8_t value);
  17. //---------------------------------------------------------------
  18. /* I2C Interfejs functions */
  19. int Open_I2C(const char* devPath, uint8_t devAddress) {
  20.     int device;
  21.     if((device = open(devPath, O_RDWR)) < 0 ) {
  22.        return (-1);
  23.     }
  24.     if((ioctl(device, I2C_SLAVE, devAddress & 0x7F)) < 0 ) {
  25.        return (-2);
  26.     }
  27.     return device;
  28. }
  29. void Close_I2C(int device) {
  30.     close(device);
  31. }
  32. static uint8_t Write_I2C(int device, uint8_t value) {
  33.     int status;
  34.     uint8_t buffer[2] = {value, 0x00};
  35.     if((status = write(device, buffer, 1))<0 ){
  36.         printf("Write Error I2C %d\n", status);
  37.         return 1;
  38.     }
  39.     usleep(2);
  40.     return 0;
  41. }
  42. //-------------------------------------------------------------
  43. static void Priv_Send_Halfbyte(int device, uint8_t c)
  44. {
  45.     c<<=4;
  46.     ENABLE_SET(device);
  47.     usleep(50);
  48.     Write_I2C(device, portlcd|c);
  49.     ENABLE_RESET(device);
  50.     usleep(50);
  51. }
  52. static void Priv_Send_Byte(int device, uint8_t c, uint8_t mode)
  53. {
  54.     uint8_t hc=0;
  55.     if (mode==0)
  56.     {
  57.        RS_RESET(device);
  58.     }
  59.     else
  60.     {
  61.        RS_SET(device);
  62.     }
  63.     hc=c>>4;
  64.     Priv_Send_Halfbyte(device, hc);
  65.     Priv_Send_Halfbyte(device, c);
  66. }
  67. //-------------------------------------------------------------
  68. void LCD_Initial(int device){
  69.     for(uint8_t i=0;i<3;i++)
  70.     {
  71.         Priv_Send_Halfbyte(device, 0x03);
  72.         usleep(45000);
  73.     }
  74.     //Wlaczenie trybu czterobitowego
  75.     Priv_Send_Halfbyte(device, 0x02);
  76.     usleep(100000);
  77.     Priv_Send_Byte(device, HD44780_FUNCTION_SET | HD44780_FONT5_7 | HD44780_TWO$
  78.     usleep(1000);
  79.     Priv_Send_Byte(device, HD44780_DISPLAY_ONOFF | HD44780_DISPLAY_OFF,0);
  80.     usleep(1000);
  81.     Priv_Send_Byte(device, HD44780_ENTRY_MODE | HD44780_EM_SHIFT_CURSOR | HD447$
  82.     usleep(1000);
  83.     LED_SET(device);
  84.     WRITE_SET(device);
  85.     LCD_Clear(device);
  86. }
  87. void LCD_Clear(int device)
  88. {
  89.     Priv_Send_Byte(device, 0x01,0);
  90.     usleep(1000000);
  91. }
  92. void LCD_Send_Char(int device, char ch)
  93. {
  94.     Priv_Send_Byte(device, ch,1);
  95. }
  96. void LCD_Send_String(int device, char* st
  97. {
  98.     uint8_t i=0;
  99.     while(st[i] != 0)
  100.     {
  101.        Priv_Send_Byte(device, st[i],1);
  102.        i++;
  103.     }
  104. }
  105. void LCD_Send_Str_Pos(int device, char* st, uint8_t x, uint8_t y)
  106. {
  107.     LCD_Set_Position(device, x,y);
  108.     LCD_Send_String(device, st);
  109. }
  110. void LCD_Set_Position(int device, uint8_t x, uint8_t y)
  111. {
  112.     switch(y)
  113.     {
  114.       case 0:
  115.           Priv_Send_Byte(device, x|0x80, 0);
  116.           usleep(1000);
  117.       break;
  118.       case 1:
  119.           Priv_Send_Byte(device, (0x40+x)|0x80, 0);
  120.           usleep(1000);
  121.       break;
  122.     }
  123. }

Plik lcd2_16.h:

  1. #ifndef LCD2_16_H_
  2. #define LCD2_16_H_
  3. #include <stdio.h>
  4. #include <stdint.h>
  5. #define I2C_FILE_NAME "/dev/i2c-1"
  6. #define I2C_DISP_ADDRESS 0x27
  7. #define ENABLE_SET(x) Write_I2C(x, portlcd |= 0x04)
  8. #define ENABLE_RESET(x) Write_I2C(x, portlcd &=~ 0x04)
  9. #define RS_SET(x) Write_I2C(x, portlcd |= 0x01)
  10. #define RS_RESET(x) Write_I2C(x, portlcd &=~ 0x01)
  11. #define LED_SET(x) Write_I2C(x, portlcd |= 0x08)
  12. #define WRITE_SET(x) Write_I2C(x, portlcd &=~ 0x02)
  13. #define HD44780_CLEAR 0x01
  14. #define HD44780_HOME 0x02
  15. #define HD44780_ENTRY_MODE 0x04
  16. #define HD44780_EM_SHIFT_CURSOR 0
  17. #define HD44780_EM_SHIFT_DISPLAY 1
  18. #define HD44780_EM_DECREMENT 0
  19. #define HD44780_EM_INCREMENT 2
  20. #define HD44780_DISPLAY_ONOFF 0x08
  21. #define HD44780_DISPLAY_OFF 0
  22. #define HD44780_DISPLAY_ON 4
  23. #define HD44780_CURSOR_OFF 0
  24. #define HD44780_CURSOR_ON 2
  25. #define HD44780_CURSOR_NOBLINK 0
  26. #define HD44780_CURSOR_BLINK 1
  27. #define HD44780_DISPLAY_CURSOR_SHIFT 0x10
  28. #define HD44780_SHIFT_CURSOR 0
  29. #define HD44780_SHIFT_DISPLAY 8
  30. #define HD44780_SHIFT_LEFT 0
  31. #define HD44780_SHIFT_RIGHT 4
  32. #define HD44780_FUNCTION_SET 0x20
  33. #define HD44780_FONT5_7 0
  34. #define HD44780_FONT5_10 4
  35. #define HD44780_ONE_LINE 0
  36. #define HD44780_TWO_LINE 8
  37. #define HD44780_4_BIT 0
  38. #define HD44780_8_BIT 16
  39. #define HD44780_CGRAM_SET 0x40
  40. #define HD44780_DDRAM_SET 0x80
  41. /* I2C */
  42. int Open_I2C(const char* devPath, uint8_t devAddress);
  43. void Close_I2C(int device);
  44. /* DISPLAY */
  45. void LCD_Initial(int device);
  46. void LCD_Clear(int device);
  47. void LCD_Send_Char(int device, char ch);
  48. void LCD_Send_String (int device, char* st);
  49. void LCD_Set_Position(int device, uint8_t x, uint8_t y);
  50. void LCD_Send_Str_Pos(int device, char* st, uint8_t x, uint8_t y);
  51. #endif /* LCD2_16_H_ */

Plik main.c:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <linux/fb.h>
  4. #include "lcd2_16.h"
  5. int main(){
  6.     int i2c_dev = Open_I2C(I2C_FILE_NAME, I2C_DISP_ADDRESS);
  7.     if(i2c_dev <0){
  8.        printf("Blad I2C: %d\n", i2c_dev);
  9.        return 1;
  10.     }
  11.     else{
  12.       printf("I2C dziala: %d\n", i2c_dev);
  13.     }
  14.     LCD_Initial(i2c_dev);
  15.     LCD_Send_Str_Pos(i2c_dev, "linia1",0,0);
  16.     LCD_Send_Str_Pos(i2c_dev, "linia2",0,1);
  17.     Close_I2C(i2c_dev);
  18.     return 0;
  19. }

W celu wykonania kompilacji programu należy w konsoli wpisać:

  1. 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:

  1. ./main