INTERFEJS I2C

SBC na iMX6 ....
https://somlabs.com/
ODPOWIEDZ
Awatar użytkownika
l3n1n
Moderator
Posty: 145
Rejestracja: 28 paź 2017, 8:46
Lokalizacja: 3M

INTERFEJS I2C

Post autor: l3n1n » 06 cze 2018, 10:59

Przyłączenie zewnętrznych układów poprzez magistralę I2C nie będzie od nas wymagało zbyt wiele pracy. Domyślny obraz, który ściągnęliśmy ze strony SomLABS i wgraliśmy do naszego VisionSoM, posiada włączony jeden z interfejsów I2C.

Aby to sprawdzić wpisujemy komendę:
root@somlabs:~# ls /sys/class/i2c-adapter
i2c-1
Jak widać dostaliśmy informację, że mamy dostępny interfejs 1.
Interfejsów możemy uruchomić więcej, ale na razie popatrzmy jak możemy wykorzystać to co już posiadamy.
Aby mieć możliwość zobaczenia naszego interfejsu w katalogu /dev należy przeprowadzić następującą instalację:
root@somlabs:~# apt install libi2c-dev i2c-tools
Aby moduł i2c-dev był ładowany podczas startu systemu należy dodać go w pliku /etc/modules. Edytujemy ten plik dowolnym edytorem i dodajemy na końcu linię i2c-dev. Zapisujemy, wychodzimy z edytora po czym musimy zrestartować system.

Po restarcie listujemy zawartość katalogu /dev i powinniśmy znaleźć interesująca nas pozycję:
root@somlabs:~# ls /dev
autofs loop4 ram13 tty1 tty3 tty5 urandom
block loop5 ram14 tty10 tty30 tty50 v4l
bus loop6 ram15 tty11 tty31 tty51 vcs
char loop7 ram2 tty12 tty32 tty52 vcs1
console mem ram3 tty13 tty33 tty53 vcs2
cpu_dma_latency memory_bandwidth ram4 tty14 tty34 tty54 vcs3
disk mmcblk1 ram5 tty15 tty35 tty55 vcs4
dri mmcblk1p1 ram6 tty16 tty36 tty56 vcs5
fd mxc_asrc ram7 tty17 tty37 tty57 vcs6
full network_latency ram8 tty18 tty38 tty58 vcsa
fuse network_throughput ram9 tty19 tty39 tty59 vcsa1
hwrng null random tty2 tty4 tty6 vcsa2
i2c-1 pps0 rfkill tty20 tty40 tty60 vcsa3
initctl ptmx rtc tty21 tty41 tty61 vcsa4
input ptp0 rtc0 tty22 tty42 tty62 vcsa5
kmsg pts shm tty23 tty43 tty63 vcsa6
log pxp_device snd tty24 tty44 tty7 vhci
loop-control ram0 stderr tty25 tty45 tty8 video0
loop0 ram1 stdin tty26 tty46 tty9 watchdog
loop1 ram10 stdout tty27 tty47 ttymxc0 watchdog0
loop2 ram11 tty tty28 tty48 ttymxc1 zero
loop3 ram12 tty0 tty29 tty49 ubi_ctrl
Teraz możemy podłączyć jakieś urządzenie do naszego VisionSoM. Podłączymy się do szyny RPI zgodnie z opisem na zamieszczonym obrazku.

Obrazek

Po podłączeniu możemy uruchomić program i2cdetect z parametrem zgodnym z numerem posiadanego przez nas interfejsu.
Ja podpiąłem trzy urządzenia, które akurat miałem pod ręką.

Obrazek

Otrzymałem taki wynik:
root@somlabs:~# i2cdetect 1
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-1.
I will probe address range 0x03-0x77.
Continue? [Y/n] Y
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- 5a -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Jak widać adresy tych urządzeń to 0x39, 0x3c oraz 0x5a.

Awatar użytkownika
l3n1n
Moderator
Posty: 145
Rejestracja: 28 paź 2017, 8:46
Lokalizacja: 3M

Re: INTERFEJS I2C

Post autor: l3n1n » 07 cze 2018, 13:22

Niestety nie mam za wiele czasu więc tylko parę zdjęć jako krótka zajawka tego co będzie :).

Obrazek

Obrazek

Obrazek

Nic wydumanego, znalezione w sieci i dostosowane do naszych potrzeb. Ważne że działa :)

Awatar użytkownika
l3n1n
Moderator
Posty: 145
Rejestracja: 28 paź 2017, 8:46
Lokalizacja: 3M

Re: INTERFEJS I2C - OLED

Post autor: l3n1n » 08 cze 2018, 12:25

Czas coś więcej napisać na temat zajawki zamieszczonej powyżej.
Wiemy już jak skonfigurować i2c, jak podłączyć moduły, jak wykryć adresy urządzeń. Czas napisać jakiś programik. Nie lubię wyważać otwartych drzwi więc znalazłem coś co chodzi pod RPI pod Linuksem. I jak się okazało po maleńkich poprawkach działa także pod naszym VisionSoM.
W związku z tym, że ciężko się pisze w konsolce Putty, program pisałem a właściwie robiłem poprawki i kompilowałem na laptopie, na Ubuntu zainstalowanym na VirtualBoxie.
Potrzebny kod znalazłem na GitHubie pod adresem: https://github.com/mikukonai/SSD1306-I2C-Linux-Driver.

Pozwolę sobie kody wkleić, po to, żeby pokazać zmiany. Pozbyłem sie także nagłówka autora, który jest dostępny pod linkiem, żeby kod był maksymalnie jasny i prosty.
Plik SSD1306.h
  1. #ifndef SSD1306_H
  2. #define SSD1306_H
  3.  
  4. #define pgm_read_byte(addr) (*(unsigned char *)(addr))
  5. #define OLEDFONT(name) static const uint8_t name[]
  6.  
  7. #include <inttypes.h>
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include <fcntl.h>
  11. #include <linux/i2c-dev.h>
  12. #include <errno.h>
  13.  
  14. #include "font8x8.h"
  15. #include "font5x7.h"
  16.  
  17. #define SSD1306_Max_X                 127    //128 Pixels
  18. #define SSD1306_Max_Y                 63     //64  Pixels
  19.  
  20. #define PAGE_MODE                     01
  21. #define HORIZONTAL_MODE               02
  22.  
  23. #define SSD1306_Address               0x3C
  24. #define SSD1306_Command_Mode          0x00//0x80
  25. #define SSD1306_Data_Mode             0x40
  26. #define SSD1306_Display_Off_Cmd       0xAE
  27. #define SSD1306_Display_On_Cmd        0xAF
  28. #define SSD1306_Normal_Display_Cmd    0xA6
  29. #define SSD1306_Inverse_Display_Cmd   0xA7
  30. #define SSD1306_Activate_Scroll_Cmd   0x2F
  31. #define SSD1306_Dectivate_Scroll_Cmd  0x2E
  32. #define SSD1306_Set_Brightness_Cmd    0x81
  33.  
  34. #define Scroll_Left                   0x00
  35. #define Scroll_Right                  0x01
  36.  
  37. #define Scroll_2Frames                0x7
  38. #define Scroll_3Frames                0x4
  39. #define Scroll_4Frames                0x5
  40. #define Scroll_5Frames                0x0
  41. #define Scroll_25Frames               0x6
  42. #define Scroll_64Frames               0x1
  43. #define Scroll_128Frames              0x2
  44. #define Scroll_256Frames              0x3
  45.  
  46. #define SSD1306_I2C_DEVFILE "/dev/i2c-1" // Raspberry Pi (Pin 3 5)
  47. #define SSD1306_I2C_ADDR 0x3c // ==(SSD1306_Address)
  48. static int ssd1306_i2c_devfd = 0;
  49.  
  50. const uint8_t* m_font;      // Current font.
  51. static uint8_t m_font_offset = 2;  // Font bytes for meta data.
  52. static uint8_t m_font_width;       // Font witdth.
  53. static uint8_t m_col;              // Cursor column.
  54. static uint8_t m_row;              // Cursor row (RAM).
  55. static char addressingMode;
  56.  
  57. void SSD1306_init(void);
  58.  
  59. void SSD1306_setNormalDisplay();
  60. void SSD1306_setInverseDisplay();
  61.  
  62. // modified
  63. void SSD1306_sendCommand(unsigned char command);
  64. void SSD1306_sendData(unsigned char Data);
  65.  
  66. void SSD1306_setPageMode();
  67. void SSD1306_setHorizontalMode();
  68.  
  69. void SSD1306_setTextXY(unsigned char Row, unsigned char Column);
  70. void SSD1306_clearDisplay();
  71. void SSD1306_setBrightness(unsigned char Brightness);
  72. int SSD1306_putChar(unsigned char c);
  73. void SSD1306_putString(const char *string);
  74. unsigned char SSD1306_putNumber(long n);
  75. unsigned char SSD1306_putFloat(float floatNumber,unsigned char decimal);
  76. void SSD1306_drawBitmap(unsigned char *bitmaparray,int bytes);
  77.  
  78. void SSD1306_setHorizontalScrollProperties(
  79.     int direction,
  80.     unsigned char startPage,
  81.     unsigned char endPage,
  82.     unsigned char scrollSpeed);
  83. void SSD1306_activateScroll();
  84. void SSD1306_deactivateScroll();
  85.  
  86. void SSD1306_setFont(const uint8_t* font);
  87.  
  88. #endif
Plik test.c
  1.  
  2. #include "SSD1306.h"
  3.  
  4. int main()
  5. {
  6.     SSD1306_init();
  7.     SSD1306_clearDisplay();
  8.         SSD1306_setBrightness(255);
  9.  
  10.         SSD1306_setPageMode();
  11.     SSD1306_setTextXY(1,2);
  12.     SSD1306_putString("FORUM SUNDUINO");
  13.         SSD1306_setTextXY(4,4);
  14.         SSD1306_putString("vISIONsOm");
  15.  
  16.     close(ssd1306_i2c_devfd);
  17.     return 0;
  18. }
Pozostałe wszystkie pliki wrzucamy do naszego katalogu z programem, tak jak są, bez żadnych zmian. Generalnie to co musiałem z tymi plikami zrobić to tylko kosmetyka, wszystko działa praktycznie od ręki. O jest to dla nas podpowiedź, że w przyszłości nie będziemy musieli wynajdować koła od nowa bo jest trochę gotowych rozwiązań. Wszystko jest proste, łatwe i przyjemne. Warunkiem jest stworzenie sobie środowiska programistycznego, które będzie ułatwiało nam pracę.
Jeszcze trochę o kompilacji.
Żeby przeprowadzić kompilację z linii komend pod Linuksem należy wpisać polecenie:
  1. gcc plik.c -o plik
Jeżeli jednak będziemy używać własnych plików nagłówkowych, czyli takich, które w kodzie umieszczacie w "lib.h" a nie w <lib.h> to niestety kompilator nie zobaczy tego co będzie zamieszczone w odpowiednim pliku lib.c. Dlatego należy w poleceniu dodać wszystkie pliki c, jakie mamy w naszym projekcie.
Aaby skompilować nasz program test.c należy wydać następujące polecenie:
  1. gcc SSD1306.c test.c -o test
Teraz program powinien się skompilować poprawnie.
Zawsze jeszcze możemy to wszystko umieścić w jednym pliku i wtedy dokonać kompilacji, jednak nie będzie to ani przejrzyste ani czytelne.
Efekty już znacie ze zdjęć powyżej. Widać, że teraz nasz VisionSoM zyskał możliwość komunikowania się z nami już nie tylko poprzez ekran terminala ale także przez ekran nader sympatycznych wyświetlaczy OLED.

Awatar użytkownika
l3n1n
Moderator
Posty: 145
Rejestracja: 28 paź 2017, 8:46
Lokalizacja: 3M

Re: INTERFEJS I2C - LCD 2x16 (HG44780)

Post autor: l3n1n » 17 lip 2018, 17:08

Sporo czasu minęło od czasu gdy pisałem ostatni post o VisionSOM. Spowodowane to było nawałem pracy zawodowej. Teraz mając chwilkę wolnego czasu postaram się ponownie was ponudzić.
Dzisiaj ciąg dalszy moich linuksowych przygód.

Tym razem wziąłem na tapetę ulubiony wyświetlacz młodych oraz początkujących elektroników-programistów czyli 2x16. Jest to tani wyświetlacz, stąd zapewne jego popularność. Prostata obsługi w wersji i2c dopełnia szczęścia i pozwala wyświetlić na ekranie to co sobie zażyczymy. Począwszy od zwykłych, pojedynczych znaków, liter, cyfr, poprzez stringi a na wynikach działań i pomiarów innych urządzeń, takich jak wszelkiej maści czujniki skończywszy. Na przykład temperatury, wilgotności, ciśnienia, napięcia, natężenia czy też takich układów jak RTC.

Jak to zrobić w przypadku VisionSOM?

Jeżeli mamy osobno moduł i osobno wyświetlacz możemy zmontować zestaw tak, jak ja to zrobiłem na płytce testowej (patrz zdjęcie na końcu posta).
Jeżeli mamy gotowy moduł LCD z przylutowanym do niego konwerterem I2C to musimy podpiąć pod znane już nam piny Rapsberry PI, umieszczone na płycie VisionSOM, ich odpowiedniki w konwerterze I2C:

Obrazek

czyli 1 z VCC, 6 z GND, 3 z SDA i 5 z SCL.

Przy prawidłowym połączeniu ekran naszego wyświetlacza zacznie świecić. Chyba, że zdejmiecie dżamperek który widać na 2 pinach po lewej stronie na zdjęciu. On właśnie służy do wyłączenie podświetlenia wyświetlacza.

Teraz pozostało nam już tylko zająć się naszym kodem.

Wykorzystamy do tego celu bibliotekę która wygląda tak:

Plik lcd.h
  1. #define I2C_OK                0
  2. #define I2C_ERROR             1
  3.  
  4. typedef unsigned char u8;
  5. typedef struct _lcd{
  6.    u8  dim;
  7.    int dev;
  8. }lcd;
  9.  
  10. void lcd_notify(lcd* l);
  11. void lcd_cmd(lcd* l, u8 cmd);
  12. void lcd_init(lcd* l, int dev);
  13. void lcd_backlight(lcd* l);
  14. void lcd_putc(lcd* l, u8 c);
  15. void lcd_print(lcd* l, const char* t, u8 len, u8 line);
  16. void lcd_clear(lcd* l);
  17. void lcd_set_cursor(lcd* l, u8 col, u8 row);
  18. void lcd_add_char(lcd* l, const unsigned char font[8], u8 position);
  19.  
  20. int open_i2c(const char* dev_path, unsigned char addr);
  21. void close_i2c(int dev);
  22. int set_i2c_data(int dev, unsigned char val);
  23. int get_i2c_data(int dev, unsigned char *value);
Plik lcd.c
  1. #include <assert.h>
  2. #include <stdio.h>
  3. #include <linux/i2c-dev.h>
  4. #include <fcntl.h>
  5. #include <stdlib.h>
  6. #include <unistd.h>
  7. #include <sys/ioctl.h>
  8. #include <string.h>
  9.  
  10. #include "lcd.h"
  11.  
  12.  
  13. void lcd_notify(lcd* l){
  14.     assert(l);
  15.     u8 v;
  16.     get_i2c_data(l->dev, &v);
  17.     set_i2c_data(l->dev, (v | 0x04) | 0x08);
  18.     get_i2c_data(l->dev, &v);
  19.     set_i2c_data(l->dev, (v & 0xFB) | 0x08);
  20. }
  21.  
  22. void lcd_cmd(lcd* l, u8 cmd){
  23.     assert(l);
  24.     set_i2c_data(l->dev, (cmd & 0xF0)   | 0x08);
  25.     lcd_notify(l);
  26.     set_i2c_data(l->dev, (cmd & 0x0F)<<4| 0x08);
  27.     lcd_notify(l);
  28.     set_i2c_data(l->dev, 0x0 | 0x08);
  29. }
  30.  
  31. void lcd_init(lcd* l, int dev){
  32.     assert(l && dev > 0);
  33.     l->dev = dev;
  34.     l->dim = 0;
  35.     set_i2c_data(l->dev, 0x30 | 0x08); // enable 8 bit mode
  36.     lcd_notify(l);
  37.     usleep(3);
  38.     set_i2c_data(l->dev, 0x20 | 0x08); // enable 4 bit mode
  39.     lcd_notify(l);
  40.     usleep(3);
  41.     lcd_notify(l);
  42.     usleep(3);
  43.  
  44.     lcd_cmd(l, 0x28);
  45.     lcd_cmd(l, 0x08);
  46.     lcd_cmd(l, 0x01);
  47.     lcd_cmd(l, 0x06);
  48.     lcd_cmd(l, 0x0C);
  49.     lcd_cmd(l, 0x0F);
  50. }
  51.  
  52. void lcd_backlight(lcd* l){
  53.     lcd_notify(l);
  54. }
  55.  
  56. void lcd_putc(lcd* l, u8 c){
  57.     assert(l);
  58.     set_i2c_data(l->dev, 0x01 | (c & 0xF0)   | 0x08);
  59.     lcd_notify(l);
  60.     set_i2c_data(l->dev, 0x01 | (c & 0x0F)<<4| 0x08);
  61.     lcd_notify(l);
  62.     set_i2c_data(l->dev, 0x0 | 0x08);
  63. }
  64.  
  65. void lcd_print(lcd* l, const char* t, u8 len, u8 line){
  66.     assert(l && t && line < 4);
  67.     u8 i;
  68.     u8 rows[] = {0x00, 0x40, 0x14, 0x54};
  69.     lcd_cmd(l, 0x80 | rows[line]);
  70.     for(i=0; i<len; ++i)
  71.         lcd_putc(l, t[i]);
  72.     lcd_cmd(l, 0xC);
  73. }
  74.  
  75. void lcd_clear(lcd* l){
  76.     assert(l);
  77.     lcd_cmd(l, 0x1);
  78.     lcd_cmd(l, 0x2);
  79. }
  80.  
  81. void lcd_set_cursor(lcd* l, u8 col, u8 row){
  82.     assert(l && row < 4);
  83.     u8 rows[] = {0x00, 0x40, 0x14, 0x54};
  84.     lcd_cmd(l, 0x80 | (col + rows[row]));
  85. }
  86.  
  87. void lcd_add_char(lcd* l, const unsigned char font[8], u8 position){
  88.     u8 j;
  89.     lcd_cmd(l, 0x40 | ((position & 7) << 3));
  90.     for(j=0; j<8; ++j)
  91.       lcd_putc(l, font[j]);
  92. }
  93. int open_i2c(const char* dev_path, unsigned char addr) {
  94.     int dev;
  95.     dev = open(dev_path, O_RDWR);
  96.  
  97.     ioctl(dev, I2C_SLAVE, addr & 0x7F);
  98.     return dev;
  99. }
  100.  
  101. void close_i2c(int dev) {
  102.     close(dev);
  103. }
  104.  
  105. int get_i2c_data(int dev, unsigned char *value) {
  106.     int r;
  107.     if((r = read(dev, value, 1))<0 ){
  108.         printf("Nie można odczytać magistrali I2C %d\n", r);
  109.         return 1;
  110.     }
  111.     return 0;
  112. }
  113.  
  114. int set_i2c_data(int dev, unsigned char val) {
  115.     int r;
  116.     unsigned char buf[2];
  117.     buf[1] = 0;
  118.     buf[0] = val;
  119.     if((r = write(dev, buf, 1))<0 ){
  120.         printf("Nie mozna zapisac na magistrali I2C %d\n", r);
  121.         return 1;
  122.     }
  123.     usleep(2);
  124.     return 0;
  125. }
Oprócz tego tworzymy nasz plik zasadniczy, w którym umieści nasza funkcje main.

lcdtest.c
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <linux/fb.h>
  4. #include "lcd.h"
  5.  
  6. #define I2C_FILE_NAME "/dev/i2c-1"
  7.  
  8. const char* txt[]  = {
  9.     "l3n1n pozdrawia",
  10.     "Forum Sunduino"
  11. };
  12.  
  13. int main(){
  14.     int i2c_dev;
  15.     lcd lcd0;
  16.     // 0x3f is the address of the i2c device
  17.     i2c_dev = open_i2c(I2C_FILE_NAME, 0x3f);
  18.     if(i2c_dev <0){
  19.        printf("Blad: %d\n", i2c_dev);
  20.        return 1;
  21.     }
  22.     lcd_init(&lcd0, i2c_dev);
  23.     lcd_clear(&lcd0);
  24.     lcd_print(&lcd0, txt[0], strlen(txt[0]), 0);
  25.     lcd_print(&lcd0, txt[1], strlen(txt[1]), 1);
  26.     close_i2c(i2c_dev);
  27.     return 0;
  28. }
Kompilujemy całość, plik wynikowy przegrywamy na naszego VisionSOM i mamy gotowe rozwiązanie. Teraz wystraczy tylko uruchomic nasz plik komendą:
./testlcd
I pojawia się na naszym wyświetlaczu wymyślony tekst.

Obrazek

Jak widać zadanie nie było trudne a efekt jest przyjemny do oka. Pozwala nam rozbudzić wyobraźnie co jeszcze możemy z tym zrobić i czego możemy dokonać :).
I jak się okazuje zestaw działa bez odrobiny termogluta :D.

ODPOWIEDZ