SPI kernelio modulis

Kadangi jau išmokome rašyti labas pasauli kernel modulius, reikia imtis ko nors rimtesnio. Šį kartą mokinsimės rašyti kernelio draiverį nusiųsti duomenims SPI įrenginiui. Kaip ir *”labas pasauli”* pavyzdyje, duomenys bus siunčiami modulio prijungimo (inicializavimo) metu.

Taip pat, dėl įdomumo panaudosime dinamiškai moduliui galimą priskirti parametrą, kurį bus galima nurodyti prijungiant modulį.

Kaip veikia SPI įrenginio prijungimas Linux kernelyje

Iš pradžių reikia susirasti masterį. Masteris randamas pagal magistralės numerį (kompiuteriukas gali turėti kelis masterius — spi kontrolerius). Mūsų pavyzdyje masterio numerį bus galima nurodyti kaip parametrą, o jeigu jis nebus nurodytas, bus imama numatytoji reikšmė — 0, kuri turėtų tikti daugeliu atvejų.

Kai jau turime masterį, reikia susikurti kanalą per kurį vyks bendravimas. To kanalo tipas yra struct spi_device*\. Sėkmingai jį sukūrus reikėtų nepamiršti jo atlaisvinti modulį atjungiant.
Sukuriant kanalą reikia nurodyti šiek tiek informacijos apie mūsų prijungiamą įrenginį (slave) — kokiu greičiu jis gali priimti duomenis, modaliasą, kokiu režimu jis dirba, naudojamas CS (chip select) numeris ir pan.

Inicializavus kanalą jau galime pradėti apsikeitinėti duomenimis. Tam galime rinktis iš daugybės SPI duomenų apsikeitimo funkcijų:

  • spi_write — siųsti duomenis sinchroniškai;
  • spi_read — gauti duomenis sinchroniškai;
  • spi_sync_transfer — sinchroniškai siųsti ir gauti duomenis tuo pačiu metu;
  • spi_w8r8 — sinchronikai išsiųsti 8 bitus ir tada nuskaityti 8 bitus;
  • spi_w8r16 — sinchronikai išsiųsti 8 bitus ir tada nuskaityti 16 bitų;
  • spi_w8r16be — sinchronikai išsiųsti 8 bitus ir tada nuskaityti 16 bitų big-endian formatu;
  • spi_async — siųsti duomenis asinchroniškai;
  • spi_async_locked — siųsti ir gauti duomenis asinchroniškai, bet užrakinti magistralę;
  • spi_sync — siųsti ir gauti duomenis sinchroniškai;
  • spi_sync_locked — siųsti duomenis sinchroniškai ir užrakinti magistralę;
  • spi_write_then_read — sinchroniškai siųsti duomenis, o po to skaityti.

Pavyzdyje nusiųsime 8 bitus ir nieko nenuskaitinėsime.

Kodas

Kodo gavosi ne tiek ir daug. Atskirai jo neaprašinėsiu, viskas matyti komentaruose.

spi_test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>

static unsigned int busnum;
module_param(busnum, uint, 0);
MODULE_PARM_DESC(busnum, "SPI bus number (magistralės nr.) (default=0)");

static struct spi_device *spi_kanalas;

static int __init spi_init(void)
{
int ret;
uint8_t komanda_kuria_siusime_irenginiui = 0b11010011;
struct spi_master *masteris;

//Informacija apie įrenginį, kuris bus prijungtas (slave'ą)
struct spi_board_info spi_irenginio_info = {
.modalias = "spi-testas",
.max_speed_hz = 1000,
.bus_num = busnum,
.mode = SPI_MODE_0,
};

//Gauname masterį, kuris bendraus su mūsų įrenginiu
printk("spi-testas: Masterio magistralės nr.: %u.\n", spi_irenginio_info.bus_num);
masteris = spi_busnum_to_master(spi_irenginio_info.bus_num);
if(!masteris)
{
printk("spi-testas: nerastas 'master'.\n");
return -ENODEV;
}

//Sukuriame kanalą, per kurį masteris bendraus su įrenginiu
spi_kanalas = spi_new_device( masteris, &spi_irenginio_info );
if(!spi_kanalas)
{
printk("spi-testas: nepavyko sukurti kanalo bendravimui su įrenginiu.\n");
return -ENODEV;
}

//Inicializuojame šį kanalą (kompiuteriuko masterio driveriai priskiria
//numatytasias reikšmes)
ret = spi_setup(spi_kanalas);
if(ret)
{
printk("spi-testas: nepavyko inicializuoti kanalo.\n");
spi_unregister_device(spi_kanalas);
return -ENODEV;
}

//Pasiruošimai baigti, jau galime siųsti duomenis:
ret = spi_write(spi_kanalas, &komanda_kuria_siusime_irenginiui, sizeof(uint8_t));
if(ret)
{
printk(KERN_ERR "spi-testas: klaida siunčiant duomenis.\n");
spi_unregister_device(spi_kanalas);
return ret;
}

return 0;
}


static void __exit spi_exit(void)
{device-tree
spi_unregister_device(spi_kanalas);
}

module_init(spi_init);
module_exit(spi_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Piktas Zuikis");
MODULE_DESCRIPTION("SPI duomenų siuntimo pavyzdys.");

Testuojame

Kaip ir *”labas pasauli”* atveju naudosime insmod komandą, tačiau dar papildomai nurodysime ir busnum parametrą. Jo nurodyti nebūtina, nes numatytoji reikšmė ir taip 0, bet ko nepadarysi dėl skaitytojo.

Kaip matome komanda nieko negražino. Vadinasi viskas gerai. Dėl viso pikto pasitikrinkime dmesg:

Jeigu jums nepasisekė ir gaunate klaidą “chipselect 0 already in use ? spidev.”, vadinasi pamiršote po LPH9157-2 bandymų atkeisti device-tree (ten bus likusi spidev sekcija).

O kaip būti tikriems, kad duomenys tikrai buvo išsiųsti? Ar nelieka nieko kito, kaip tik pasitikėti dmesg žinute? Ne, visada į pagalbą galima pasitelkti osciloskopą. Taip ir padarykime:

Viskas taip, kaip ir turėtų būti — CLK signalas yra, duomenų signalas (MOSI) irgi atitinka siunčiamus duomenis, o CS yra žemas (0). Apačioje netgi iššifruoti siunčiami duomenys 0b11010011 atitinka kintamojo komanda_kuria_siusime_irenginiui reikšmę.

Kaip visada, kodą rasite github’e.