spi子系統之驅動SSD1306 OLED

来源:http://www.cnblogs.com/hackfun/archive/2016/11/20/6082489.html
-Advertisement-
Play Games

spi子系統之驅動SSD1306 OLED 接觸Linux之前,曾以為讀源碼可以更快的學習軟體,於是前幾個博客都是一邊讀源碼一邊添加註釋,甚至精讀到每一行代碼,實際上效果並不理想,看過之後就忘記了。主要原因是沒理解透程式架構,各個模塊之間的關係,如何聯繫在一起,再加上沒有實例驗證。後來逐漸發現,理解 ...


spi子系統之驅動SSD1306 OLED

 

接觸Linux之前,曾以為讀源碼可以更快的學習軟體,於是前幾個博客都是一邊讀源碼一邊添加註釋,甚至精讀到每一行代碼,實際上效果並不理想,看過之後就忘記了。主要原因是沒理解透程式架構,各個模塊之間的關係,如何聯繫在一起,再加上沒有實例驗證。後來逐漸發現,理解框架能達到事半功倍的效果,理解框架之後,反而代碼也更容易看懂,甚至可以猜部分代碼的作用,印象更加深刻。

 

理解SPI的驅動框架,還是從最基本的三個入口點觸發,platform_device,platform_bus,platform_driver。

 

其中內核一提供給platform_bus,platform_driver在spi_s3c24xx_gpio.c和spi_s3c24xxc.c中,其中spi_s3c24xx_gpio.c用於IO模擬SPI (本例討論的是IO模擬SPI),spi_s3c24xxc.c用於s3c24xx的硬體SPI。因此,我們需要動手寫一個platform_device。

 

看看spi_s3c24xx_gpio.c做了些什麼。

 1 static int s3c2410_spigpio_probe(struct platform_device *dev)
 2 {
 3     ... ...
 4     /* [cgw]: 分配一個SPI主機 */
 5     master = spi_alloc_master(&dev->dev, sizeof(struct s3c2410_spigpio));
 6     ... ...
 7 
 8     sp = spi_master_get_devdata(master);
 9 
10     platform_set_drvdata(dev, sp);
11 
12     /* [cgw]: 分配與spi硬體相關的配置,如指定哪些IO為MISO,MOSI,SCLK,CS,SPI工作模式,最大時鐘等等 */
13     /* copy in the plkatform data */
14     sp->info = dev->dev.platform_data;
15 
16     /* [cgw]: 提供實現SPI各種模式的時序的基本方法,和CS的激活方法 */
17     /* setup spi bitbang adaptor */
18     sp->bitbang.master = spi_master_get(master);
19     sp->bitbang.chipselect = s3c2410_spigpio_chipselect;
20 
21     sp->bitbang.txrx_word[SPI_MODE_0] = s3c2410_spigpio_txrx_mode0;
22     sp->bitbang.txrx_word[SPI_MODE_1] = s3c2410_spigpio_txrx_mode1;
23     sp->bitbang.txrx_word[SPI_MODE_2] = s3c2410_spigpio_txrx_mode2;
24     sp->bitbang.txrx_word[SPI_MODE_3] = s3c2410_spigpio_txrx_mode3;
25 
26     /* [cgw]: 配置相關io為輸入輸出 */
27     /* set state of spi pins */
28     s3c2410_gpio_setpin(sp->info->pin_clk, 0);
29     s3c2410_gpio_setpin(sp->info->pin_mosi, 0);
30 
31     s3c2410_gpio_cfgpin(sp->info->pin_clk, S3C2410_GPIO_OUTPUT);
32     s3c2410_gpio_cfgpin(sp->info->pin_mosi, S3C2410_GPIO_OUTPUT);
33     s3c2410_gpio_cfgpin(sp->info->pin_miso, S3C2410_GPIO_INPUT);
34 
35     /* [cgw]: 設置spi的收發,如註冊一個工作隊列,收發時序的方法,8/16/32的spi數據等等 */
36     ret = spi_bitbang_start(&sp->bitbang);
37     ... ...
38 
39     /* [cgw]: 註冊sp->info->board_size個spi設備,這幾個spi設備都是掛接在統一spi匯流排上的 */
40     /* register the chips to go with the board */
41     for (i = 0; i < sp->info->board_size; i++) {
42         dev_info(&dev->dev, "registering %p: %s\n",
43              &sp->info->board_info[i],
44              sp->info->board_info[i].modalias);
45 
46         sp->info->board_info[i].controller_data = sp;
47         spi_new_device(master, sp->info->board_info + i);
48     }
49     ... ...
50 }

 

  1. 註冊了一個platform_driver
  2. 在s3c2410_spigpio_probe中,分配並註冊了一個spi主機,並註冊了掛接在這個SPI主機上的所有spi設備

要想s3c2410_spigpio_probe得到調用,即探測到有效的platform_device,我們需要一個與platform同名("s3c24xx-spi-gpio")的platform_device。

 1 static struct spi_board_info board_info[1] = {
 2     {
 3     .modalias = "spi_ssd1306",    /* [cgw]: spi設備名,和設備驅動名對應 */
 4     .bus_num = 0,                 /* [cgw]: spi匯流排號,即spi0 */
 5     .chip_select = 2,             /* [cgw]: spi匯流排上的設備號,即spi0.2 */
 6     .max_speed_hz    = 50000,     /* [cgw]: spi時鐘 */
 7     .mode = SPI_MODE_3,           /* [cgw]: spi數據模式 */
 8     },
 9 };
10 
11 static struct s3c2410_spigpio_info spi_dev = {
12     .pin_clk = S3C2410_GPG7,
13     .pin_mosi = S3C2410_GPG5,
14     .pin_miso = S3C2410_GPG6,
15     .board_size = 1,                    /* [cgw]: 設置板上spi介面數量為1 */
16     .board_info = &board_info[0],
17     .chip_select = ssd1306_chip_select
18 };
19 
20 static struct platform_device spi_platform_dev = {
21     .name         = "s3c24xx-spi-gpio",        /* [cgw]: 設置平臺設備名,和平臺驅動名對應 */
22     .id           = -1,
23     .dev = { 
24         .release = spi_dev_release,
25         .platform_data = (void *)&spi_dev,      /* [cgw]: 通過platform_data傳遞spi_dev給平臺驅動
26                                                 * 平臺驅動可以訪問spi_dev
27                                                 */
28     },
29 };
30 
31 static int spi_dev_init(void)
32 {
33     /* [cgw]: 註冊spi_platform_dev平臺設備 */
34     platform_device_register(&spi_platform_dev);
35     return 0;
36 }

 

spi_bitbang.c提供了spi底層一些實現細節,註冊工作隊列(SPI數據的傳送最終是通過調用工作隊列實現的),spi工作模式,工作頻率等。

 

  1 int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
  2 {
  3     struct spi_bitbang_cs    *cs = spi->controller_state;
  4     u8            bits_per_word;
  5     u32            hz;
  6 
  7     if (t) {
  8         /* [cgw]: spi驅動指定幾位數據模式,和傳送速度 */
  9         bits_per_word = t->bits_per_word;
 10         hz = t->speed_hz;
 11     } else {
 12         bits_per_word = 0;
 13         hz = 0;
 14     }
 15 
 16     /* [cgw]: 根據spi位數,選擇合適的時序 */
 17     /* spi_transfer level calls that work per-word */
 18     if (!bits_per_word)
 19         bits_per_word = spi->bits_per_word;
 20     if (bits_per_word <= 8)
 21         cs->txrx_bufs = bitbang_txrx_8;
 22     else if (bits_per_word <= 16)
 23         cs->txrx_bufs = bitbang_txrx_16;
 24     else if (bits_per_word <= 32)
 25         cs->txrx_bufs = bitbang_txrx_32;
 26     else
 27         return -EINVAL;
 28 
 29     /* [cgw]: 設置SCLK的時鐘頻率 */
 30     /* nsecs = (clock period)/2 */
 31     if (!hz)
 32         hz = spi->max_speed_hz;
 33     if (hz) {
 34         cs->nsecs = (1000000000/2) / hz;
 35         if (cs->nsecs > (MAX_UDELAY_MS * 1000 * 1000))
 36             return -EINVAL;
 37     }
 38 
 39     return 0;
 40 }
 41 
 42 int spi_bitbang_setup(struct spi_device *spi)
 43 {
 44     struct spi_bitbang_cs    *cs = spi->controller_state;
 45     struct spi_bitbang    *bitbang;
 46     int            retval;
 47 
 48     bitbang = spi_master_get_devdata(spi->master);
 49 
 50     /* REVISIT: some systems will want to support devices using lsb-first
 51      * bit encodings on the wire.  In pure software that would be trivial,
 52      * just bitbang_txrx_le_cphaX() routines shifting the other way, and
 53      * some hardware controllers also have this support.
 54      */
 55     /* [cgw]: 預設不支持LSB模式,要想使用LSB模式,只要bitbang_txrx_le_cphaX()改變移位的方向即可 */
 56     if ((spi->mode & SPI_LSB_FIRST) != 0)
 57         return -EINVAL;
 58 
 59     if (!cs) {
 60         cs = kzalloc(sizeof *cs, GFP_KERNEL);
 61         if (!cs)
 62             return -ENOMEM;
 63         spi->controller_state = cs;
 64     }
 65 
 66     /* [cgw]: 設置spi的預設位數 */
 67     if (!spi->bits_per_word)
 68         spi->bits_per_word = 8;
 69 
 70     /* per-word shift register access, in hardware or bitbanging */
 71     /* [cgw]: 設置spi的工作模式,四種 */
 72     cs->txrx_word = bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)];
 73     if (!cs->txrx_word)
 74         return -EINVAL;
 75 
 76     /* [cgw]: 調用spi_bitbang_setup_transfer */
 77     retval = bitbang->setup_transfer(spi, NULL);
 78     if (retval < 0)
 79         return retval;
 80 
 81     dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n",
 82             __FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
 83             spi->bits_per_word, 2 * cs->nsecs);
 84 
 85     /* NOTE we _need_ to call chipselect() early, ideally with adapter
 86      * setup, unless the hardware defaults cooperate to avoid confusion
 87      * between normal (active low) and inverted chipselects.
 88      */
 89 
 90     /* [cgw]: spi忙的話,通過改變CS的狀態釋放SPI */
 91     /* deselect chip (low or high) */
 92     spin_lock(&bitbang->lock);
 93     if (!bitbang->busy) {
 94         bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
 95         ndelay(cs->nsecs);
 96     }
 97     spin_unlock(&bitbang->lock);
 98 
 99     return 0;
100 }
101 
102 
103 static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t)
104 {
105     struct spi_bitbang_cs    *cs = spi->controller_state;
106     unsigned        nsecs = cs->nsecs;
107     
108     /* [cgw]: 具體數據收發就是這裡實現的 */
109     return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t);
110 }
111 
112 int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
113 {
114     struct spi_bitbang    *bitbang;
115     unsigned long        flags;
116     int            status = 0;
117 
118     m->actual_length = 0;
119     m->status = -EINPROGRESS;
120 
121     bitbang = spi_master_get_devdata(spi->master);
122 
123     spin_lock_irqsave(&bitbang->lock, flags);
124     if (!spi->max_speed_hz)
125         status = -ENETDOWN;
126     else {
127         /* [cgw]: 入隊一個工作到工作隊列 */
128         list_add_tail(&m->queue, &bitbang->queue);
129         queue_work(bitbang->workqueue, &bitbang->work);
130     }
131     spin_unlock_irqrestore(&bitbang->lock, flags);
132 
133     return status;
134 }
135 
136 int spi_bitbang_start(struct spi_bitbang *bitbang)
137 {
138     int    status;
139 
140     if (!bitbang->master || !bitbang->chipselect)
141         return -EINVAL;
142     
143     /* [cgw]: 註冊一個工作隊列 */
144     INIT_WORK(&bitbang->work, bitbang_work);
145     spin_lock_init(&bitbang->lock);
146     INIT_LIST_HEAD(&bitbang->queue);
147 
148     /* [cgw]: 配置相關方法 */
149     if (!bitbang->master->transfer)
150         bitbang->master->transfer = spi_bitbang_transfer;
151     if (!bitbang->txrx_bufs) {
152         bitbang->use_dma = 0;
153         bitbang->txrx_bufs = spi_bitbang_bufs;
154         if (!bitbang->master->setup) {
155             if (!bitbang->setup_transfer)
156                 bitbang->setup_transfer =
157                      spi_bitbang_setup_transfer;
158             bitbang->master->setup = spi_bitbang_setup;
159             bitbang->master->cleanup = spi_bitbang_cleanup;
160         }
161     } else if (!bitbang->master->setup)
162         return -EINVAL;
163 
164     /* [cgw]: 創建一個單線程,用於調度工作隊列 */
165     /* this task is the only thing to touch the SPI bits */
166     bitbang->busy = 0;
167     bitbang->workqueue = create_singlethread_workqueue(
168             bitbang->master->cdev.dev->bus_id);
169     if (bitbang->workqueue == NULL) {
170         status = -EBUSY;
171         goto err1;
172     }
173 
174     /* [cgw]: 註冊一個spi主機 */
175     /* driver may get busy before register() returns, especially
176      * if someone registered boardinfo for devices
177      */
178     status = spi_register_master(bitbang->master);
179     if (status < 0)
180         goto err2;
181 
182     return status;
183 
184 err2:
185     destroy_workqueue(bitbang->workqueue);
186 err1:
187     return status;
188 }

 

因為在s3c2410_spigpio_probe中註冊了spi的設備,因此我們還需為這些設備提供驅動,以被這些設備探測到,探測這些驅動的條件也是設備和驅動的名字同名,即spi_ssd1306。我們這裡提供了一個ssd1306 OLED的驅動

 1 static struct spi_driver spi_ssd1306_driver = {
 2     .driver = {
 3         .name    = "spi_ssd1306",
 4         .bus    = &spi_bus_type,
 5         .owner    = THIS_MODULE,
 6     },
 7     .probe    = spi_ssd1306_probe,
 8     .remove    = __devexit_p(spi_ssd1306_remove),
 9 };
10 
11 static int spi_ssd1306_init(void)
12 {
13     return spi_register_driver(&spi_ssd1306_driver);
14 }

 

到這裡,基本工作已經完成。怎樣驅動ssd1306 OLED呢?

ssd1306 OLED的使用方法,請參考相關的手冊。

本例提供的ssd1306 OLED驅動,只需要我們提供一個基本9位spi數據收發的介面即可。

 

static void ssd1306_write_byte(uint8_t chData, uint8_t chCmd) 
{
    struct spi_transfer t;
    struct spi_message m;

    uint16_t data = chData;
    
    /* [cgw]: 情況spi_transfer */
    memset(&t,0,sizeof(struct spi_transfer));
    
    /* [cgw]: 第9位表示前8位是命令還是數據,1:數據,0:命令 */    
    if (chCmd) {
        data |= (1 << 8);
    } else {
        data &= ~(1 << 8);
    }

    /* [cgw]: 要發送的數據 */
    t.tx_buf = &data;
    /* [cgw]: 長度,2位元組 */
    t.len = 2; 
    /* [cgw]: 9位spi */
    t.bits_per_word = 9;
    //t.cs_change = 1;
    /* [cgw]: 把數據添加到收發列表,工作隊列調度時會從收發隊列中取出,併進行收發
     * 註意這裡並沒有直接收發
     */
    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    spi_sync(spi_ssd1306_dev, &m);
}

 

註意,在網上看到一些例子,用8位模式驅動ssd1306 OLED的,需要用DC的狀態來表示數據或命令的,他們的做法如下:

 

 1 void ssd1306_write_cmd(uint8_t cmd)
 2 {
 3     ssd1306_dc_clr();
 4     spi_write(cmd);
 5     ssd1306_dc_set();
 6 }
 7 
 8 void ssd1306_write_data(uint8_t data)
 9 {
10     ssd1306_dc_set();
11     spi_write(data);
12     ssd1306_dc_clr();
13 }

 

 

我本人認為是不正確的,至少不符合這個spi框架的邏輯,因為spi數據的收發並不是直接在spi_write()實現,而是在工作隊列bitbang_work()中實現。儘管這樣仍然能驅動ssd1306 OLED,但理論上不應該這麼做。要改的話應該改bitbang_work()中改,添加DC狀態的控制。

 

 

  1 static void bitbang_work(struct work_struct *work)
  2 {
  3     struct spi_bitbang    *bitbang =
  4         container_of(work, struct spi_bitbang, work);
  5     unsigned long        flags;
  6 
  7     spin_lock_irqsave(&bitbang->lock, flags);
  8     bitbang->busy = 1;
  9     /* [cgw]: 隊列不為空 */
 10     while (!list_empty(&bitbang->queue)) {
 11         struct spi_message    *m;
 12         struct spi_device    *spi;
 13         unsigned        nsecs;
 14         struct spi_transfer    *t = NULL;
 15         unsigned        tmp;
 16         unsigned        cs_change;
 17         int            status;
 18         int            (*setup_transfer)(struct spi_device *,
 19                         struct spi_transfer *);
 20 
 21         /* [cgw]: 取出spi_message */
 22         m = container_of(bitbang->queue.next, struct spi_message,
 23                 queue);
 24         /* [cgw]: 刪除這個節點 */
 25         list_del_init(&m->queue);
 26         /* [cgw]: 進入臨界區 */
 27         spin_unlock_irqrestore(&bitbang->lock, flags);
 28 
 29         /* FIXME this is made-up ... the correct value is known to
 30          * word-at-a-time bitbang code, and presumably chipselect()
 31          * should enforce these requirements too?
 32          */
 33         nsecs = 100;
 34 
 35         spi = m->spi;
 36         tmp = 0;
 37         cs_change = 1;
 38         status = 0;
 39         setup_transfer = NULL;
 40 
 41         /* [cgw]: 歷遍spi_message中的收發列表 */
 42         list_for_each_entry (t, &m->transfers, transfer_list) {
 43 
 44             /* override or restore speed and wordsize */
 45             /* [cgw]: 如果驅動指定了spi速度,和位數,重新調用spi_bitbang_setup_transfer
 46              * 更改預設設置
 47              */
 48             if (t->speed_hz || t->bits_per_word) {
 49                 setup_transfer = bitbang->setup_transfer;
 50                 if (!setup_transfer) {
 51                     status = -ENOPROTOOPT;
 52                     break;
 53                 }
 54             }
 55             if (setup_transfer) {
 56                 status = setup_transfer(spi, t);
 57                 if (status < 0)
 58                     break;
 59             }
 60 
 61             /* set up default clock polarity, and activate chip;
 62              * this implicitly updates clock and spi modes as
 63              * previously recorded for this device via setup().
 64              * (and also deselects any other chip that might be
 65              * selected ...)
 66              */
 67             /* [cgw]: 激活spi */
 68             if (cs_change) {
 69                 bitbang->chipselect(spi, BITBANG_CS_ACTIVE);
 70                 ndelay(nsecs);
 71             }
 72             /* [cgw]: 驅動指定收發完一幀數據要不要改變恢復CS為空閑 */
 73             cs_change = t->cs_change;
 74             /* [cgw]: 收發包為空,則無效 */
 75             if (!t->tx_buf && !t->rx_buf && t->len) {
 76                 status = -EINVAL;
 77                 break;
 78             }
 79 
 80             /* transfer data.  the lower level code handles any
 81              * new dma mappings it needs. our caller always gave
 82              * us dma-safe buffers.
 83              */
 84             if (t->len) {
 85                 /* REVISIT dma API still needs a designated
 86                  * DMA_ADDR_INVALID; ~0 might be better.
 87                  */
 88                 if (!m->is_dma_mapped)
 89                     t->rx_dma = t->tx_dma = 0;
 90                 /* [cgw]: 這裡才是真正的實現spi收發時序 */
 91                 status = bitbang->txrx_bufs(spi, t);
 92             }
 93             if (status != t->len) {
 94                 if (status > 0)
 95                     status = -EMSGSIZE;
 96                 break;
 97             }
 98             m->actual_length += status;
 99             status = 0;
100 
101             /* protocol tweaks before next transfer */
102             if (t->delay_usecs)
103                 udelay(t->delay_usecs);
104             
105             /* [cgw]: 收發完一幀,不改變CS狀態 */
106             if (!cs_change)
107                 continue;
108             
109             /* [cgw]: 收發列表已經沒有數據,結束 */
110             if (t->transfer_list.next == &m->transfers)
111                 break;
112 
113             /* sometimes a short mid-message deselect of the chip
114              * may be needed to terminate a mode or command
115              */
116             /* [cgw]: 釋放spi */
117             ndelay(nsecs);
118             bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
119             ndelay(nsecs);
120         }
121 
122         m->status = status;
123         m->complete(m->context);
124 
125         /* restore speed and wordsize */
126         /* [cgw]: 速度和位數恢復預設 */
127         if (setup_transfer)
128             setup_transfer(spi, NULL);
129 
130         /* normally deactivate chipselect ... unless no error and
131          * cs_change has hinted that the next message will probably
132          * be for this chip too.
133          */
134         if (!(status == 0 && cs_change)) {
135             ndelay(nsecs);
136             bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
137             ndelay(nsecs);
138         }
139 
140         spin_lock_irqsave(&bitbang->lock, flags);
141     }
142     bitbang->busy = 0;
143     /* [cgw]: 退出臨界區 */
144     spin_unlock_irqrestore(&bitbang->lock, flags);
145 }

 

代碼:

spi_platform_dev.c

 1 #include <asm/arch/spi-gpio.h>
 2 
 3 
 4 static struct spi_board_info board_info[1] = {
 5     {
 6     .modalias = "spi_ssd1306",    /* [cgw]: spi設備名,和設備驅動名對應 */
 7     .bus_num = 0,                 /* [cgw]: spi匯流排號,即spi0 */
 8     .chip_select = 2,             /* [cgw]: spi匯流排上的設備號,即spi0.2 */
 9     .max_speed_hz    = 50000,     /* [cgw]: spi時鐘 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 恢復內容開始 系統環境:Windows 10 && CentOS 7 準備工具: ●CentOS7 官網下載地址:https://wiki.centos.org/Download (註意: i386是32位) ●EaseUS Partition Master是一個非常強大的磁碟分區工具,可以將分區格 ...
  • 本文主要討論和分析在UEFI+GPT模式下的Windows系統(主要是最新的Win10X64)中預設的分區結構和預設的分區大小,硬碟整數分區、4K對齊、起始扇區、恢復分區、ESP分區、MSR分區,哪些分區是必要的,刪除是否會影響系統運行。 ...
  • 安裝Linux時一般會自動識別windows系統並添加引導項,而 CentOS 預設不支持 NTFS 分區,CentOS7採用了Grub2,與CentOS6有區別。手動引導windows分區修改配置稍不一樣。 一、自動尋找分區 網上不少使用命令: grub2-mkconfig -o /boot/gr ...
  • 1.帶緩存和不帶緩存 系統調用是不帶緩存的,使用它編寫程式,需要程式員設置緩存;庫函數是帶緩存的,不需設置緩存。 2.三個位元組流 程式執行時自動打開三個位元組流,標準輸入、標準輸入和標準錯誤。 3.標準輸出重定向 4.標準輸入重定向 5.標準錯誤重定向 6.管道 可以將一個命令的位元組流輸出導向另一個命 ...
  • top 幾個磁碟fdisk -l 磁碟空間 df -lhdf -al 查看進程:ps -ef“grep java殺死進程:kill -9 進程號 more中過濾 more xxx |grep www.makaidong.com 安裝linux後配置ip Vim /etc/sysconfig/netw ...
  • 1.進入vi的命令 vi filename :打開或新建文件,並將游標置於第一行首 【新建文件】vi +n filename :打開文件,並將游標置於第n行首 【比如:某個shell報錯的行數時使用】vi + filename :打開文件,並將游標置於最後一行首 【追加功能】vi +/pattern ...
  • 命令是一種可執行文件,包括:關鍵字 參數。 1.顯示目錄中的文件 2.查詢工作目錄 3.改變文件許可權mask 4.以root身份執行命令 5.查看幫助 6.創建文件 7.複製文件 8.移動文件 9.刪除文件 10.創建目錄 11.刪除空目錄 12.改變文件訪問許可權 13.改變文件的擁有者 14.改變 ...
  • 新的2016MBP終於發佈了,作為把蘋果電腦裝WIN使用的人,等候很久之後,終於可以行動了。 1、之前的2013款Macbook Air 2013年之前一直用的是DELL電腦,由於DELL鍵盤左下角的Fn鍵在Ctrl鍵的右側,已經慣了多年,所以一直對於ThinkPad和Apple這樣Fn和Ctrl鍵... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...