拟定一个农产品电商网站的建设需求软件开发外包合同模板

当前位置: 首页 > news >正文

拟定一个农产品电商网站的建设需求,软件开发外包合同模板,时代网站管理系统怎么做网站,安卓软件开发工程师周日业余时间太无聊#xff0c;又不喜欢玩游戏#xff0c;大家的兴趣爱好都是啥#xff1f;我觉得敲代码也是一种兴趣爱好。正巧手边有一块儿0.96寸的OLED显示屏#xff0c;一直在吃灰#xff0c;何不把玩一把#xff1f;于是说干就干#xff0c;最后在我的imax6ul的lin… 周日业余时间太无聊又不喜欢玩游戏大家的兴趣爱好都是啥我觉得敲代码也是一种兴趣爱好。正巧手边有一块儿0.96寸的OLED显示屏一直在吃灰何不把玩一把于是说干就干最后在我的imax6ul的linux开发板上使用spi用户态驱动成功点亮。这里总结下过程分享给有需要的小伙伴。 前言 本文主要介绍在imax6ul-mini开发板上如何驱动OLED显示屏外设总结下过程。由于板子默认是spi接口的这里先玩一把spi接口的驱动后续计划改为i2c的接口驱动再玩一次。 我的环境资源 Linux内核linux-4.1.15 所用开发板正点原子imax6ul-mini 所用OLED 屏幕中景园电子0.96 寸OLED 显示屏12864液晶屏模块支持spi和i2c接口 所用OLED 驱动芯片SH1106 完整源码下载地址 https://download.csdn.net/download/qq8864/88117562 效果截图 实现方案 想要驱动中景园电子的这款OLED显示屏方案有很多。这个模块同时支持spi和i2c接口所以肯定需要使用linux的spi或i2c驱动。 以 spi驱动为例在嵌入式Linux下实现SPI驱动的方式有多种。以下是其中几种常见的方式

  1. 使用GPIO控制模拟SPI使用GPIO接口控制SPI总线的时序和数据传输需要自行编写驱动程序来实现SPI通信。
  2. 使用SPI框架驱动Linux内核提供了SPI框架驱动可以通过注册SPI设备和驱动来实现SPI通信。需要编写SPI设备和驱动的代码。
  3. 使用spidev驱动spidev是Linux内核提供的一个通用的SPI设备驱动接口可以简化SPI设备的使用。它提供了用户空间的API通过打开/dev/spidev设备文件使用ioctl函数进行SPI通信。使用spidev驱动可以方便地在用户空间进行SPI通信而无需编写内核驱动程序。 4.使用fbtft小屏幕驱动识别出fbx的framebuffer节点Linux内核里已经提供spi接口小屏的设备驱动fbtft在drivers/staging/fbtft路径下可以查找相似或新增加新的驱动这个一般更适用于spi接口的tft彩色液晶小屏幕。 这里要介绍的实现方式是使用spidev驱动驱动。原因是因为操作OLED屏幕需要自定义一系列的操作接口如oled_dispString() oled_clear()等。虽然使用方式2是较为流行的一种但是需要注册到字符设备框架下提供文件系统的操作接口的方式用用起来稍显麻烦还需再次封装一下。 spidev驱动是一个通用的SPI设备驱动接口它允许用户空间通过简单的API与SPI设备进行通信。用户可以通过打开/dev/spidev设备文件使用ioctl函数进行SPI通信。spidev驱动提供了一些常用的操作函数如配置SPI模式、设置时钟频率、传输数据等。使用spidev驱动可以方便地在用户空间进行SPI通信而无需编写内核驱动程序。 与传统的SPI驱动相比spidev驱动的优点是简单易用无需编写内核驱动程序只需在用户空间使用ioctl函数进行SPI通信。但是spidev驱动也有一些限制例如无法支持中断和DMA传输等高级功能。如果需要使用这些高级功能可能需要编写自定义的SPI驱动程序。 实现过程 首先需要确认内核配置开启了spidev驱动。参见博文嵌入式linux通用spi驱动之spidev使用总结_特立独行的猫a的博客-CSDN博客 修改设备树 修改imx6ull-14x14-evk.dts文件该设备树文件位于内核源码 linux/arch/arm/boot/dts/目录下。 ecspi3 {fsl,spi-num-chipselects 2;/cs管脚数配置/cs-gpios 0,gpio1 20 GPIO_ACTIVE_LOW;/cs管脚配置/pinctrl-names default;pinctrl-0 pinctrl_ecspi3;status okay;/* status属性值为okay 表示该节点使能*/spidev: icm206080 {compatible alientek,icm20608;spi-max-frequency 8000000;reg 0;/spi设备是没有设备地址的, 这里是指使用spi控制器的cs-gpios里的第几个片选io /};oled: oledsh11061 {compatible yang,oledsh1106;/重要会匹配spidev.c中指定的compatible/spi-cpol;/配置spi信号模式/spi-cpha;spi-max-frequency 8000000 ;/* 指定spi设备的最大工作时钟 /reg 1;}; }; 以上需要注意的是如果该spi接口下挂载有多个从设备需要设置fsl,spi-num-chipselects 2;默认该值为1。还有需要注意的地方是cs-gpios 片选信号需要配置对应的个数。以上的为配置了两路片选GPIO管脚第一个默认的第二个是指定的。如果仅有一个从设备可以配置cs-gpio就行了。注意cs-gpio和cs-gpios的区别带s的标识可以有多个。 修改spidev驱动 默认的spidev.c驱动文件中是没有匹配你添加的设备的。因此需要修改spidev.c源码增加compatible匹配。spidev.c源码文件位于linux/drivers/spi/spidev.c / The main reason to have this class is to make mdev/udev create the* /dev/spidevB.C character device nodes exposing our userspace API.* It also simplifies memory management.*/static struct class *spidev_class;//#ifdef CONFIG_OF static const struct of_device_id spidev_dt_ids[] {{ .compatible rohm,dh2228fv },{ .compatible yang,oledsh1106 },{}, }; MODULE_DEVICE_TABLE(of, spidev_dt_ids); //#endif 编译内核和设备树 #加载环境 source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi #编译内核 make zImage -j16 #编译指定的设备树 make imx6ull-14x14-nand-4.3-480x272-c.dtb 为了方便调试更新内核和设备树文件建议最好使用sd卡启动这样把sd卡抠出来插入电路上可以很方便的更新内核和设备树文件。  设备树查看 内核和设备树更新后启动开发板。可以看下spidev驱动是否生效了。 查看设备树是否有新添加的节点 更新设备树到板子上后能够查看到如下生成spi设备节点(spidev2.1) 经过以上过程已经成功了一大半啦。或者可以用工具测试下spi驱动接口。 OELD驱动实现 在以上spidev总线驱动就绪的基础上OELD驱动实现就简单啦。 先看下oled模块板子的接线 DO对应SPI接口的CLK, D1(spi)数据线对应SPI接口了MOSI。 注意由于这个屏幕显示不存在读取的情况SPI的MISO口并未使用且莫把线接错了。(比如我一开始就把MISO接到DC上啦这是错的。)。DC口是啥这是该模块的数据和命令选择管脚分为发送指令和发送数据两周类型的操作。当发送指令时DC口需要输出高电平当发送数据时DC口需要发送低电平。 SPISerial Peripheral Interface是一种串行外设接口协议用于在微控制器或其他设备之间进行通信。SPI使用四根线进行通信SCLK时钟线、MOSI主设备输出从设备输入线、MISO主设备输入从设备输出线和SS片选线。 MOSIMaster Out Slave In是主设备向从设备发送数据的线路而MISOMaster In Slave Out是从设备向主设备发送数据的线路。这两条线的功能是相反的主设备通过MOSI将数据发送给从设备从设备通过MISO将数据发送给主设备。 SPI协议中的数据传输是双向的主设备和从设备可以同时发送和接收数据。因此如果只需要进行写操作理论上可以只使用MOSI线而将MISO线悬空不连接。但在实际应用中为了保持SPI接口的完整性和稳定性通常还是会将MISO线连接起来即使在写操作时不使用。 连接MISO线的好处是可以实现双向通信的灵活性以备将来可能需要读取从设备的数据。另外MISO线上的数据也可以用于进行错误检测和校验以提高数据传输的可靠性。 驱动实现 spidev驱动操作 使用spidev驱动的操作方法大致过程操作如下
  4. 打开SPI设备 int spi_fd; spi_fd open(/dev/spidev0.0, O_RDWR); if (spi_fd 0) {perror(Failed to open SPI device);return -1; }
  5. 设置SPI模式、速度和位数 int mode SPI_MODE_0; int speed 1000000; int bits_per_word 8; if (ioctl(spi_fd, SPI_IOC_WR_MODE, mode) 0) {perror(Failed to set SPI mode);return -1; } if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, speed) 0) {perror(Failed to set SPI speed);return -1; } if (ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, bits_per_word) 0) {perror(Failed to set SPI bits per word);return -1; }
  6. 创建spi_ioc_message结构体并设置相关字段 struct spi_ioc_transfer transfer; memset(transfer, 0, sizeof(struct spi_ioc_transfer)); transfer.tx_buf (unsigned long)tx_buffer;  // 发送缓冲区 transfer.rx_buf (unsigned long)rx_buffer;  // 接收缓冲区 transfer.len length;  // 数据长度 transfer.speed_hz speed;  // 传输速度 transfer.bits_per_word bits_per_word;  // 每个字的位数 transfer.cs_change 1;  // 控制片选信号的行为 其中transfer.cs_change为1表示每次传输前会拉低片选信号传输完成后会拉高片选信号。
  7. 发送命令和数据 if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), transfer) 0) {perror(Failed to send SPI message);return -1; } 其中SPI_IOC_MESSAGE(1)表示发送1个spi_ioc_transfer结构体。 OELD驱动接口封装 #include stdint.h #include unistd.h #include stdio.h #include stdlib.h #include string.h #include getopt.h #include fcntl.h #include sys/ioctl.h #include linux/types.h #include linux/spi/spidev.h #include oledfont.h #include bmp.h #include oled.h#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))static void pabort(const char *s) {perror(s);abort(); }static const char *device /dev/spidev2.1;static int32_t spi_fd; static uint32_t spi_mode; static uint8_t spi_bits 8; static uint32_t spi_speed 800000; static uint16_t spi_delay; static int verbose;static void hex_dump(const void *src, size_t length, size_t line_size, char *prefix) {int i 0;const unsigned char *address src;const unsigned char *line address;unsigned char c;printf(%s | , prefix);while (length– 0) {printf(%02X , address);if (!(i % line_size) || (length 0 i % line_size)) {if (length 0) {while (i % line_size)printf(__ );}printf( | ); / right close */while (line address) {c line;printf(%c, (c 33 || c 255) ? 0x2E : c);}printf(\n);if (length 0)printf(%s | , prefix);}} }/** Unescape - process hexadecimal escape character converts shell input \x23 - 0x23*/ static int unescape(char *_dst, char *_src, size_t len) {int ret 0;char *src _src;char *dst _dst;unsigned int ch;while (*src) {if (*src \ *(src1) x) {sscanf(src 2, %2x, ch);src 4;*dst (unsigned char)ch;} else {*dst *src;}ret;}return ret; }static int transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) {int ret;struct spi_ioc_transfer tr {.tx_buf (unsigned long)tx,.rx_buf (unsigned long)rx,.len len,.delay_usecs spi_delay,.speed_hz spi_speed,.bits_per_word spi_bits,.cs_change 0,};if (spi_mode SPI_TX_QUAD)tr.tx_nbits 4;else if (spi_mode SPI_TX_DUAL)tr.tx_nbits 2;if (spi_mode SPI_RX_QUAD)tr.rx_nbits 4;else if (spi_mode SPI_RX_DUAL)tr.rx_nbits 2;if (!(spi_mode SPI_LOOP)) {if (spi_mode (SPI_TX_QUAD | SPI_TX_DUAL))tr.rx_buf 0;else if (spi_mode (SPI_RX_QUAD | SPI_RX_DUAL))tr.tx_buf 0;}ret ioctl(fd, SPI_IOC_MESSAGE(1), tr);if (ret 1)pabort(cant send spi message);if (verbose){hex_dump(tx, len, 32, TX);hex_dump(rx, len, 32, RX);}return ret; }//向SSD1106写入一个字节。 //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { u8 tx[2];u8 rx[2];tx[0] dat;if(cmd){system(echo 1 /sys/class/gpio/gpio1/value);transfer(spi_fd,tx,rx,1);}else{system(echo 0 /sys/class/gpio/gpio1/value);transfer(spi_fd,tx,rx,1);}
    } //初始化SSD1306
    void OLED_Init(void) { //OLED_RST_Set();usleep(100000);//OLED_RST_Clr();usleep(100000);//OLED_RST_Set(); OLED_WR_Byte(0xAE,OLED_CMD);//–turn off oled panelOLED_WR_Byte(0x02,OLED_CMD);//—set low column addressOLED_WR_Byte(0x10,OLED_CMD);//—set high column addressOLED_WR_Byte(0x40,OLED_CMD);//–set start line address Set Mapping RAM Display Start Line (0x00~0x3F)OLED_WR_Byte(0x81,OLED_CMD);//–set contrast control registerOLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current BrightnessOLED_WR_Byte(0xA1,OLED_CMD);//–Set SEG/Column Mapping 0xa0左右反置 0xa1正常OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常OLED_WR_Byte(0xA6,OLED_CMD);//–set normal displayOLED_WR_Byte(0xA8,OLED_CMD);//–set multiplex ratio(1 to 64)OLED_WR_Byte(0x3f,OLED_CMD);//–164 dutyOLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)OLED_WR_Byte(0x00,OLED_CMD);//-not offsetOLED_WR_Byte(0xd5,OLED_CMD);//–set display clock divide ratio/oscillator frequencyOLED_WR_Byte(0x80,OLED_CMD);//–set divide ratio, Set Clock as 100 Frames/SecOLED_WR_Byte(0xD9,OLED_CMD);//–set pre-charge periodOLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks Discharge as 1 ClockOLED_WR_Byte(0xDA,OLED_CMD);//–set com pins hardware configurationOLED_WR_Byte(0x12,OLED_CMD);OLED_WR_Byte(0xDB,OLED_CMD);//–set vcomhOLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect LevelOLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)OLED_WR_Byte(0x02,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//–set Charge Pump enable/disableOLED_WR_Byte(0x14,OLED_CMD);//–set(0x10) disableOLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) OLED_WR_Byte(0xAF,OLED_CMD);//–turn on oled panelOLED_WR_Byte(0xAF,OLED_CMD); /display ON/ OLED_Clear();OLED_Set_Pos(0,0); }void OLED_Set_Pos(unsigned char x, unsigned char y) { OLED_WR_Byte(0xb0y,OLED_CMD);OLED_WR_Byte(((x0xf0)4)|0x10,OLED_CMD);OLED_WR_Byte((x0x0f)|0x01,OLED_CMD); }
    //开启OLED显示
    void OLED_Display_On(void) {OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令OLED_WR_Byte(0X14,OLED_CMD); //DCDC ONOLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON } //关闭OLED显示
    void OLED_Display_Off(void) {OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFFOLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF } //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
    void OLED_Clear(void)
    { u8 i,n; for(i0;i8;i) { OLED_WR_Byte (0xb0i,OLED_CMD); //设置页地址0~7OLED_WR_Byte (0x02,OLED_CMD); //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n0;n128;n)OLED_WR_Byte(0,OLED_DATA); } //更新显示 }//在指定位置显示一个字符,包括部分字符 //x:0~127 //y:0~63 //mode:0,反白显示;1,正常显示 //size:选择字体 1612 void OLED_ShowChar(u8 x,u8 y,u8 chr) { unsigned char c0,i0; cchr- ;//得到偏移后的值 if(xMax_Column-1){x0;yy2;}if(SIZE 16){OLED_Set_Pos(x,y); for(i0;i8;i)OLED_WR_Byte(F8X16[c*16i],OLED_DATA);OLED_Set_Pos(x,y1);for(i0;i8;i)OLED_WR_Byte(F8X16[c*16i8],OLED_DATA);}else { OLED_Set_Pos(x,y1);for(i0;i6;i)OLED_WR_Byte(F6x8[c][i],OLED_DATA);} } //m^n函数 u32 oled_pow(u8 m,u8 n) {u32 result1; while(n–)result*m; return result; }
    //显示2个数字 //x,y :起点坐标 //len :数字的位数 //size:字体大小 //mode:模式 0,填充模式;1,叠加模式 //num:数值(0~4294967295);
    void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size) { u8 t,temp;u8 enshow0; for(t0;tlen;t){temp(num/oled_pow(10,len-t-1))%10;if(enshow0t(len-1)){if(temp0){OLED_ShowChar(x(size/2)*t,y, );continue;}else enshow1; }OLED_ShowChar(x(size/2)*t,y,temp0); } } //显示一个字符号串 void OLED_ShowString(u8 x,u8 y,u8 *chr) {unsigned char j0;while (chr[j]!\0){ OLED_ShowChar(x,y,chr[j]);x8;if(x120){x0;y2;}j;} } //显示汉字 void OLED_ShowCHinese(u8 x,u8 y,u8 no) { u8 t,adder0;OLED_Set_Pos(x,y); for(t0;t16;t){OLED_WR_Byte(Hzk[2no][t],OLED_DATA);adder1;} OLED_Set_Pos(x,y1); for(t0;t16;t){ OLED_WR_Byte(Hzk[2*no1][t],OLED_DATA);adder1;} } /********功能描述显示显示BMP图片128×64起始点坐标(x,y),x的范围0127y为页的范围07************/ void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]) { unsigned int j0;unsigned char x,y;if(y1%80) yy1/8; else yy1/81;for(yy0;yy1;y){OLED_Set_Pos(x0,y);for(xx0;xx1;x){ OLED_WR_Byte(BMP[j],OLED_DATA); }} } int spidev_init() {int ret 0;spi_mode SPI_MODE_0;verbose 0;spi_fd open(device, O_RDWR);if (spi_fd 0)pabort(cant open device);/ spi mode/ret ioctl(spi_fd, SPI_IOC_WR_MODE32, spi_mode);if (ret -1)pabort(cant set spi mode);ret ioctl(spi_fd, SPI_IOC_RD_MODE32, spi_mode);if (ret -1)pabort(cant get spi mode);/** bits per word/ret ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, spi_bits);if (ret -1)pabort(cant set bits per word);ret ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, spi_bits);if (ret -1)pabort(cant get bits per word);/** max speed hz/ret ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, spi_speed);if (ret -1)pabort(cant set max speed hz);ret ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, spi_speed);if (ret -1)pabort(cant get max speed hz);printf(spi mode: 0x%x\n, spi_mode);printf(bits per word: %d\n, spi_bits);printf(max speed: %d Hz (%d KHz)\n, spi_speed, spi_speed/1000);return ret; } 测试使用 int main(int argc, char *argv[]) {//导出DC口这里使用的是GPIO1管脚作为DC口使用(命令数据选择管脚)system(echo 1 /sys/class/gpio/export);system(echo out /sys/class/gpio/gpio1/direction);spidev_init();OLED_Init();OLED_ShowString(0,0,hello world);return 0; } 注意这里使用了一种偷懒的做法直接导出了一个GPIO1管脚使用通过system调用的方式先导出IO口。在OLED的发送接口里也通过了system命令调用的方式相当低效且费资源正式用的话肯定不这么做这里仅是为了测试。 gpiochipxx当前SoC 所包含的GPIO 控制器I.MX6UL/I.MX6ULL 一共包含了5 个GPIO控制器分别为GPIO1、GPIO2、GPIO3、GPIO4、GPIO5在这里分别对应gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128 这5 个文件夹每一个gpiochipxx 文件夹用来管理一组GPIO。 例如要导出GPIO1_1可以通过这种方式 echo 1 /sys/class/gpio/export 对于给定的一个GPIO 引脚如何计算它在sysfs 中对应的编号呢其实非常简单譬如给定一个GPIO引脚为GPIO4_IO16那它对应的编号是多少呢首先我们要确定GPIO4 对应于gpiochip96该组GPIO 引脚的最小编号是96对应于GPIO4_IO0所以GPIO4_IO16 对应的编号自然是96 16 112同理GPIO3_IO20 对应的编号是64 20 84。 //向SSD1106写入一个字节。 //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { u8 tx[2];u8 rx[2];tx[0] dat;if(cmd){system(echo 1 /sys/class/gpio/gpio1/value);transfer(spi_fd,tx,rx,1);}else{system(echo 0 /sys/class/gpio/gpio1/value);transfer(spi_fd,tx,rx,1);}
    } 使用 echo 命令通过文件系统导出和控制 GPIO 口是一种简单易用的方式但在性能要求较高的场景下可能不够高效。可以考虑使用 GPIO 库、设备驱动程序或用户空间库和工具来提高效率如可以使用libgpiod库的方式。  libgpiod库简介 libgpiod是用于与linux GPIO交互的C库和工具从 linux 4.8 后官方不推荐使用 GPIO sysfs 接口libgpiod库封装了 ioctl 调用和简单的API接口。Libgpiod是一种字符设备接口GPIO访问控制是通过操作字符设备文件比如/dev/gpiodchip0实现的。 与sysfs方式相比libgpiod可以保证所有分配的资源在关闭文件描述符后得到完全释放并且拥有sysfs方式接口中不存在的功能(如时间轮询一次设置/读取多个gpio值)。此外libgpiod还包含一组命令行工具允许用户使用脚本对gpio进行个性化操作。 详细介绍libgpiod/libgpiod.git - C library and tools for interacting with the linux GPIO character device libgpiod库的简单使用 #include gpiod.h #include stdio.hint main() {struct gpiod_chip *chip;struct gpiod_line *line;int value;// 打开 GPIO 控制器chip gpiod_chip_open(/dev/gpiochip0);if (!chip) {perror(Failed to open GPIO chip);return -1;}// 获取 GPIO 口line gpiod_chip_get_line(chip, 17);if (!line) {perror(Failed to get GPIO line);gpiod_chip_close(chip);return -1;}// 设置 GPIO 口为输出模式int ret gpiod_line_request_output(line, example, 0);if (ret 0) {perror(Failed to set GPIO line as output);gpiod_line_release(line);gpiod_chip_close(chip);return -1;}// 控制 GPIO 输出高低电平ret gpiod_line_set_value(line, 1);if (ret 0) {perror(Failed to set GPIO line value);gpiod_line_release(line);gpiod_chip_close(chip);return -1;}// 读取 GPIO 输入值value gpiod_line_get_value(line);printf(GPIO value: %d\n, value);// 释放资源gpiod_line_release(line);gpiod_chip_close(chip);return 0; } 上述示例中首先通过 gpiod_chip_open 打开指定的 GPIO 控制器例如 /dev/gpiochip0 然后使用 gpiod_chip_get_line 获取指定的 GPIO 口例如 17 号口。接下来我们使用 gpiod_line_request_output 将 GPIO 口设置为输出模式并使用 gpiod_line_set_value 控制输出高低电平。最后我们使用 gpiod_line_get_value 读取 GPIO 输入值并使用 gpiod_line_release 和 gpiod_chip_close 释放资源。 需要注意的是使用 libgpiod 需要安装相应的库文件和头文件并在编译时链接 libgpiod 库。 需要注意的是libgpiod 要求 Linux 内核版本至少为 4.8因为它依赖于内核的 GPIO 字符设备接口gpiochip。在 4.8 版本之前的内核中该接口可能不存在或不完整因此无法使用 libgpiod 库。可以使用如下的方式 int main(int argc, char *argv[]) {//导出DC口这里使用的是GPIO1管脚作为DC口使用(命令数据选择管脚)//system(echo 1 /sys/class/gpio/export);//system(echo out /sys/class/gpio/gpio1/direction);dc_p fopen(/sys/class/gpio/export,w);fprintf(dc_p,%d,1);fclose(dc_p);dc_p fopen(/sys/class/gpio/gpio1/direction,w);fprintf(dc_p,out);fclose(dc_p);dc_p fopen(/sys/class/gpio/gpio1/value,w);fprintf(dc_p,1);fflush(dc_p);spidev_init();OLED_Init();OLED_ShowString(0,0,hello world);return 0; } //向SSD1106写入一个字节。 //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { u8 tx[2];u8 rx[2];tx[0] dat;if(cmd){//system(echo 1 /sys/class/gpio/gpio1/value);fprintf(dc_p,1);fflush(dc_p);transfer(spi_fd,tx,rx,1);}else{//system(echo 0 /sys/class/gpio/gpio1/value);fprintf(dc_p,0);fflush(dc_p);transfer(spi_fd,tx,rx,1);}
    }
    也可以使用open系统调用函数。究竟哪个更高效需要考虑到多个因素包括数据量、缓冲区大小、系统调用次数等。以上推荐使用fopen和fwrite. fopen 和 open 是 Linux 中用于打开文件的两个函数它们之间有一些区别。
  8. fopen 是 C 标准库中的函数用于以流的形式打开文件。它返回一个 FILE* 类型的指针可以使用标准库函数如 fread 、 fwrite 、 fprintf 等对文件进行读写操作。 fopen 函数提供了一些方便的功能如自动缓冲、格式化输入输出等。但是由于它是标准库函数因此在处理大量数据时可能会有性能上的损失。
  9. open 是系统调用函数用于以文件描述符的形式打开文件。它返回一个整数类型的文件描述符可以使用系统调用函数如 read 、 write 、 ioctl 等对文件进行读写操作。 open 函数是操作系统提供的原始接口直接与内核交互因此在处理大量数据时通常比 fopen 更高效。 总的来说如果你只是进行简单的文件读写操作并且希望使用标准库函数进行处理那么可以选择使用 fopen 。但是如果你需要更高效的文件操作并且需要使用系统调用函数进行底层控制那么可以选择使用 open 。  fwrite 是 C 标准库中的函数用于将数据写入文件。它会先将数据写入缓冲区然后再将缓冲区的数据写入文件。相比之下 write 是一个系统调用直接将数据写入文件不经过缓冲区。 对于小数据量的写入操作 fwrite 的效率可能会更高因为它可以将多个小数据一次性写入缓冲区然后一次性写入文件减少了系统调用的次数。 而对于大数据量的写入操作 write 的效率可能更高。因为 fwrite 需要将数据先写入缓冲区然后再写入文件这个过程可能会涉及到多次的缓冲区刷新操作。而 write 直接将数据写入文件减少了缓冲区刷新的开销。 此外还需要考虑到缓冲区大小的影响。如果缓冲区大小适合数据量 fwrite 可能会有更好的性能。但是如果数据量超过了缓冲区大小 fwrite 需要多次刷新缓冲区可能导致性能下降。 总的来说对于小数据量的写入操作 fwrite 可能更高效。而对于大数据量的写入操作 write 可能更高效。但是具体的效率还需要根据具体的场景和需求进行测试和评估。 使用系统调用的方式如下 int main(int argc, char argv[]) {//导出DC口这里使用的是GPIO1管脚作为DC口使用(命令数据选择管脚)//system(echo 1 /sys/class/gpio/export);//system(echo out /sys/class/gpio/gpio1/direction);system(echo 1 /sys/class/gpio/unexport);ssize_t bytes_written;const char value 1;// 打开 /sys/class/gpio/export 文件fd_dc open(/sys/class/gpio/export, O_WRONLY,0644);if (fd_dc -1) {// 处理打开文件失败的情况printf(open error\n);return -1;}bytes_written write(fd_dc, 1, 1);close(fd_dc);if (bytes_written -1) {// 处理写入文件失败的情况printf(write failed: %s\n, strerror(errno));return -1;}// 打开 /sys/class/gpio/gpio1/direction 文件fd_dc open(/sys/class/gpio/gpio1/direction, O_WRONLY,0644);if (fd_dc -1) {// 处理打开文件失败的情况printf(open error1\n);return -1;}bytes_written write(fd_dc, out, 3);close(fd_dc);if (bytes_written -1) {// 处理写入文件失败的情况printf(write failed1: %s\n, strerror(errno));return -1;}// 打开 /sys/class/gpio/gpio1/value 文件fd_dc open(/sys/class/gpio/gpio1/value, O_WRONLY,0644);if (fd_dc -1) {// 处理打开文件失败的情况printf(open error2\n);return -1;}bytes_written write(fd_dc, value, strlen(value));fsync(fd_dc);spidev_init();OLED_Init();OLED_ShowString(0,0,hello world);return 0; } 在调用 open 函数时可以通过第二个参数指定文件的访问权限。权限参数是一个八进制数表示文件的读、写和执行权限。 以下是一些常用的权限参数值
  • O_RDONLY 只读模式表示打开文件以供读取。

  • O_WRONLY 只写模式表示打开文件以供写入。

  • O_RDWR 读写模式表示打开文件以供读取和写入。

  • O_CREAT 如果文件不存在则创建文件。

  • O_EXCL 与 O_CREAT 一起使用如果文件已经存在则返回错误。

  • O_TRUNC 如果文件存在并且以写入模式打开则将文件截断为零长度。

  • O_APPEND 以追加模式打开文件即写入时将数据追加到文件末尾。 权限参数可以与上述标志位进行位运算以指定文件的访问权限。例如若要以读写模式打开文件并在文件不存在时创建它可以使用以下权限参数 int fd open(filename.txt, O_RDWR | O_CREAT, 0644); 在上述示例中 0644 是一个八进制数表示文件的权限。其中 0 表示八进制数 6 表示用户文件所有者具有读写权限 4 表示组用户具有只读权限 4 表示其他用户具有只读权限。 需要注意的是权限参数只在创建文件时起作用对于已经存在的文件权限参数不会改变文件的权限。文件的实际权限由文件系统的权限控制决定。 最后为了测试OLED写一段简单的makefile方便编译。

    test spidev-oled

    #

    Copyright © 2023 yangyongzhen 5234117529qq.com

    #CC ? arm-linux-gnueabihf-gcc AR ? arm-linux-gnueabihf-ar STRIP ? stripCFLAGS ? -O2

    When debugging, use the following instead

    #CFLAGS : -O -g CFLAGS -Wall SOCFLAGS : -fpic -D_REENTRANT \((CFLAGS)#KERNELVERSION : \)(shell uname -r).PHONY: all strip clean all:$(CC) spidev_oled.c -o spidev_oledclean:rm -rf *.o 测试demo int main(int argc, char *argv[]) {//导出DC口这里使用的是GPIO1管脚作为DC口使用(命令数据选择管脚)//system(echo 1 /sys/class/gpio/export);//system(echo out /sys/class/gpio/gpio1/direction);u8 t;dc_p fopen(/sys/class/gpio/export,w);fprintf(dc_p,%d,1);fclose(dc_p);dc_p fopen(/sys/class/gpio/gpio1/direction,w);fprintf(dc_p,out);fclose(dc_p);dc_p fopen(/sys/class/gpio/gpio1/value,w);fprintf(dc_p,1);fflush(dc_p);spidev_init();OLED_Init();while(1) { OLED_Clear();OLED_ShowCHinese(0,0,0);//中OLED_ShowCHinese(18,0,1);//景OLED_ShowCHinese(36,0,2);//园OLED_ShowCHinese(54,0,3);//电OLED_ShowCHinese(72,0,4);//子OLED_ShowCHinese(90,0,5);//科OLED_ShowCHinese(108,0,6);//技OLED_ShowString(0,3,1.3 OLED TEST);//OLED_ShowString(8,2,ZHONGJINGYUAN); // OLED_ShowString(20,4,2014/05/01); OLED_ShowString(0,6,ASCII:); OLED_ShowString(63,6,CODE:); OLED_ShowChar(48,6,t);//显示ASCII字符 t;if(t~)t ;OLED_ShowNum(103,6,t,3,16);//显示ASCII字符的码值 delay_ms(8000);OLED_Clear();delay_ms(8000);OLED_DrawBMP(0,0,128,8,BMP1); //图片显示(图片显示慎用生成的字表较大会占用较多空间FLASH空间8K以下慎用)delay_ms(8000);OLED_DrawBMP(0,0,128,8,BMP2);delay_ms(8000);} } 工程完整源码下载地址 https://download.csdn.net/download/qq8864/88117562 其他资源 嵌入式Linux——IIC总线驱动3IIC驱动OLED外设_iic怎么唤醒外设_moxue10的博客-CSDN博客 Linux系统GPIO应用编程_linux控制gpio程序_行稳方能走远的博客-CSDN博客 linux系统基于syfs控制gpio_gpio linux sys_嵌入式Linux开发的博客-CSDN博客 交叉编译开源代码(以libgpiod为例)_libgpiod交叉编译_仰望南极光的博客-CSDN博客 [imx6ull应用开发]GPIO编程之LED灯设备控制—sysfs方式和libgpiod方式_gpiod_line_request_output_WH^2的博客-CSDN博客 飞凌嵌入式技术帖——i.MX9352的GPIO怎么用_dts_设备_操作 [imx6ull应用开发]GPIO编程之LED灯设备控制—sysfs方式和libgpiod方式_gpiod_line_request_output_WH^2的博客-CSDN博客 C语言方式(libgpiod) - Sipeed Wiki Libgpiod库的使用点亮LED_猪突猛进进进的博客-CSDN博客 小屏幕(SPI屏)ST77系列ili93系列NV3030等屏幕基于设备树的驱动-OpenEdv-开源电子网 干货来了如何让你的正点原子IMX6ULL开发板支持TFT系列小屏幕SPI接口的ST7789为例-OpenEdv-开源电子网