RK3568之SPI子系统(协议)
第1章 基础知识1.1 SPI介绍SPI是一个同步的数据总线也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的完美同步。时钟是一个振荡信号它告诉接收端在确切的时机对数据线上的信号进行采样。产生时钟的一侧称为主机另一侧称为从机。总是只有一个主机一般来说可以是微控制器/MCU但是可以有多个从机后面详细介绍数据的采集时机可能是时钟信号的上升沿从低到高或下降沿从高到低。SPI是全双工且SPI没有定义速度限制一般的实现通常能达到甚至超过10 Mbps1.2 一主一从的连接方式1.3 一主多从的连接方式1.4 SPI的特性SPI总线包括4条逻辑线定义如下MISOMaster input slave output主机输入从机输出数据来自从机MOSIMaster output slave input主机输出从机输入数据来自主机SCLKSerial Clock串行时钟信号由主机产生发送给从机SSSlave Select片选信号由主机发送以控制与哪个从机通信通常是低电平有效信号。其他制造商可能会遵循其他命名规则但是最终他们指的相同的含义。以下是一些常用术语MISO也可以是SIMODOUTDOSDO或SO在主机端;MOSI也可以是SOMIDINDISDI或SI在主机端;NSS也可以是CECS或SSEL;SCLK也可以是SCK;本文将按照以下命名进行讲解[MISO, MOSI, SCKNSS]。1.5 SPI设备选择SPI是单主设备 single-master 通信协议这意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写从设备时它首先拉低从设备对应的SS线SS是低电平有效接着开始发送工作脉冲到时钟线上在相应的脉冲时间上主设备把信号发到MOSI实现“写”同时可对MISO采样而实现“读”如下图1.6 SPI数据发送接收SPI主机和从机都有一个串行移位寄存器主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。首先拉低对应SS信号线表示与该设备进行通信主机通过发送SCLK时钟信号来告诉从机写数据或者读数据这里要注意SCLK时钟信号可能是低电平有效也可能是高电平有效因为SPI有四种模式这个我们在下面会介绍主机(Master)将要发送的数据写到发送数据缓存区(Menory)缓存区经过移位寄存器(0~7)串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据这样两个移位寄存器中的内容就被交换。SPI只有主模式和从模式之分没有读和写的说法外设的写操作和读操作是同步完成的。如果只进行写操作主机只需忽略接收到的字节反之若主机要读取从机的一个字节就必须发送一个空字节来引发从机的传输。也就是说你发一个数据必然会收到一个数据你要收一个数据必须也要先发一个数据。每当主机产生一个时钟脉冲SCK就会发生以下同步动作数据交换主机的寄存器向从机推入 1 bit 数据通过 MOSI同时从机的寄存器也向主机推入 1 bit 数据通过 MISO。等价交换在一个时钟周期内双方必须同时发送和接收 1 bit。经过 8 个时钟周期双方就完美地“互换”了一个字节的数据。所以SPI 在物理层面上确实是严格的“你发一个我就得收一个我想收一个就必须发一个”的等价交换模式。1.7 SPI通信的四种模式SPI通信有4种不同的操作模式不同的从设备可能在出厂是就是配置为某种模式这是不能改变的但我们的通信双方必须是工作在同一模式下所以我们可以对我们的主设备的SPI模式进行配置通过CPOL时钟极性和CPHA时钟相位来控制我们主设备的通信模式具体如下1.7.1 CPOL时钟极性CPOL决定了我们时钟线SCLK空闲时的状态。CPOL0的时候表示SCLK在空闲时为低电平。第1个跳变沿是上升沿第2个跳变沿是下降沿CPOL1的时候表示SCLK在空闲时为高电平。第1个跳变沿是下降沿第2个跳变沿是上升沿1.7.2 CPHA时钟相位CPHA是用来控制数据是在第一个跳变沿还是第二个跳变沿采集的。CPHA0的时候数据是在第一个跳变沿的时候就会被采集。CPHA1的时候数据是在第二个跳变沿的时候才会被采集。1.7.3 模式0CPOL 0CPHA 0CPOL 0时钟线空闲时是低电平第1个跳变沿是上升沿第2个跳变沿是下降沿CPHA 0数据在第1个跳变沿上升沿采样时序图如下1.7.4 模式1CPOL 0CPHA 1CPOL 0空闲时是低电平第1个跳变沿是上升沿第2个跳变沿是下降沿CPHA 1数据在第2个跳变沿下降沿采样时序图如下1.7.5 模式2CPOL 1CPHA 0CPOL 1空闲时是高电平第1个跳变沿是下降沿第2个跳变沿是上升沿CPHA 0数据在第1个跳变沿下降沿采样时序图如下1.7.6 模式3 CPOL 1CPHA 1CPOL 1空闲时是高电平第1个跳变沿是下降沿第2个跳变沿是上升沿CPHA 1数据在第2个跳变沿上升沿采样时序图如下1.8 SPI的优缺点1.8.1 SPI通讯的优点使SPI作为串行通信接口脱颖而出的原因很多全双工串行通信高速数据传输速率。简单的软件配置极其灵活的数据传输不限于8位它可以是任意大小的字非常简单的硬件结构。从站不需要唯一地址与I2C不同。从机使用主机时钟不需要精密时钟振荡器/晶振与UART不同。不需要收发器与CAN不同。1.8.2 SPI通讯的缺点没有硬件从机应答信号主机可能在不知情的情况下无处发送通常仅支持一个主设备需要更多的引脚与I2C不同没有定义硬件级别的错误检查协议与RS-232和CAN总线相比只能支持非常短的距离1.9 SPI协议开发经验。1. SPI可以是一主多从的协议。当有多个从机时一个从机需要一个片选口。2. MISO一定要配置为上拉输入或者浮空输入。3. 片选口、时钟口和MOSI口要配置为推挽输出。4. 由于时钟是几十兆数量级的所以比I2C的几十K时钟高。导致SPI通讯比I2C通讯快。(1) SPI支持全双工通讯也就是主机同时发送数据以及主机同时接收数据。(2) 主机在发送一个字节给从机时是先将这个字节的高位先发送出去。例如0xA4 1010 0100。先发最高位的1再发送次高位的0接着发送1。这样从高到低的搬运数据。(3) 虽然SPI支持全双工通讯但是同样支持一下两种情况。主机只是发送数据但是主机不接收数据主机不接收数据的本质是主机接收到数据但是不处理。从机只是发送数据但是从机不接收数据。第2章 OLEDSPI协议2.1 OLED简介OLED是(OLED即有机发光二极管又称为有机电激光显示Organic Electroluminescence Display OLED。OLED由于同时具备自发光不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性被认为是下一代的平面显示器新兴应用技术以下是OLED显示屏模块的参数尺寸0.96寸模块电压DC3~5V分辨率128×64驱动SSD1306管脚数7针接口SPI/IIC工作温度-20℃~70℃2.2 OLED的引脚描述引脚名称描述GND电源地VCC电源正3~5VD0SPI时钟线D1SPI数据线RES复位DCSPI数据/命令选择线CSSPI片选2.3工作原理SSD1306显存与命令SSD1306的显存总共为128*64bit大小其将这些显存分为了8页。每页包含了128个字节总共8页这样刚好是128*64的点阵大小。命令0X81设置对比度。包含两个字节第一个0X81为命令随后发送的一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。命令0XAE/0XAF0XAE为关闭显示命令0XAF为开启显示命令。命令0XB0~B7用于设置页地址其低三位的值对应着GRAM的页地址。命令0X00~0X0F用于设置显示时的起始列地址低四位。命令0X10~0X1F用于设置显示时的起始列地址高四位。2.4 SSD1306的4线SPI通讯时序、1参考手册12参考手册2从这个时序图可以看出几个关键点1. 在发送数据之前要把CS引脚拉低。2. D/C一般是用来控制发送命令还是数据的。3. 数据在第二个SCLK跳变开始传输。4. 数据在SCLK上升沿的时候进行传输每次发送一个Bit高位在前。所以由这个时序图可以判断出来用的是SPI模式3的通讯方式。知道这个之后你就知道单片机的硬件SPI模式要怎么配置了(下图以STM32为例)。2.5 硬件原理图OLED的引脚名称描述RK3568底板网络标号RK3568的GPIO口引脚编号GND电源地VCC电源正3~5VD0SPI时钟线SPI0_CLK_M1GPIO2_D391D1SPI数据线SPI0_MOSI_M1GPIO2_D189RES复位PDM_SDI3_M0_ADCGPIO1_B040DCDC 就是Data/Command 选择引脚SSD1306 用它来判断你通过 SPI 发过来的这个字节是命令还是数据DVP_PWREN0_H_GPIO0_B0GPIO0_B08CSSPI片选SPI0_CS0_M1GPIO2_D290注意SPI 总线标准只有 4 根线CLK、MOSI、MISO、CS。但是OLED SPI并没有使用MISO这个引脚。但 SSD1306 额外需要一根DCData/Command引脚来区分你发的是命令还是数据主机往该引脚上写0。即 DC 0 → 发送的是命令如清屏、设置对比度主机往该引脚上写1。即 DC 1 → 发送的是显存数据这根线不属于 SPI 控制器管理所以必须用一个普通 GPIO 来控制在设备树里单独声明。这里我选择GPIO0_B0作为DC引脚。2.6 查看引脚复用(编写设备树之前)cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pinsOLED的引脚名称RK3568的引脚编号复用功能CLK91CS90MOSI89MISO88配置上但是不使用DC8RES402.7 设备树编写在SPI0控制器节点下编写从机设备节点1具有SPI模式配置和大小端配置spi0{ /*覆盖原有spi0控制器中的属性配置*/ status okay; pinctrl-names default, high_speed; pinctrl-0 spi0m1_pins spi0m1_cs0; pinctrl-1 spi0m1_pins_hs spi0m1_cs0_hs; /*spi0m1的从机1*/ ssd1306_oled: ssd13060 { compatible solomon,ssd1306-spi; status okay; reg 0; /* CS0 */ spi-max-frequency 10000000; /* 10MHz */ /*spi的工作模式*/ spi-cpol; /* CPOL1 */ spi-cpha; /* CPHA1 */ /*spi先从低位开始传递*/ spi-lsb-first; /*特有的dc引脚和复位引脚*/ dc-gpios gpio0 RK_PB0 GPIO_ACTIVE_HIGH; rst-gpios gpio1 RK_PB0 GPIO_ACTIVE_LOW; }; };(2) 使用SPI的模式0spi0{ /*覆盖原有spi0控制器中的属性配置*/ status okay; pinctrl-names default, high_speed; pinctrl-0 spi0m1_pins spi0m1_cs0; pinctrl-1 spi0m1_pins_hs spi0m1_cs0_hs; /*spi0m1的从机1*/ ssd1306_oled: ssd13060 { compatible solomon,ssd1306-spi; status okay; reg 0; /* CS0 */ spi-max-frequency 10000000; /* 10MHz */ /*特有的dc引脚和复位引脚*/ dc-gpios gpio0 RK_PB0 GPIO_ACTIVE_HIGH; rst-gpios gpio1 RK_PB0 GPIO_ACTIVE_LOW; }; };编译并烧录内核2.8 查看引脚复用(编写设备树之后但是没有加载驱动)cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pinsOLED的引脚名称RK3568的引脚编号复用功能CLK91CS90MOSI89MISO88配置上但是不使用DC8RES402.9 查看设备树1查看SPI控制器0cd /proc/device-tree/spife610000/ ls -alh cat compatible2查看自定义的oled节点cd /proc/device-tree/spife610000/ssd13060/ ls -alh cat compatible echo \n第3章 stm32f103c8t6的spi协议驱动oled3.1 硬件接线OLED引脚STM32引脚D0 (CLK)PA5D1 (MOSI)PA7RESPA4DCPA3CSPA2VCC3.3VGNDGND3.2 驱动代码/** * STM32F103C8T6 SH1106 OLED 软件模拟SPI 测试程序 * * 接线: * OLED D0(CLK) - PA5 * OLED D1(MOSI) - PA7 * OLED RES - PA4 * OLED DC - PA3 * OLED CS - PA2 * OLED VCC - 3.3V * OLED GND - GND * * 使用标准库main.c 直接替换即可 * 不需要任何其他文件纯 GPIO 模拟 SPI */ #include stm32f10x.h /* ------------------------------------------------------------------ */ /* GPIO 引脚定义 */ /* ------------------------------------------------------------------ */ #define OLED_GPIO GPIOA #define OLED_CLK_PIN GPIO_Pin_5 /* D0: SPI时钟 */ #define OLED_MOSI_PIN GPIO_Pin_7 /* D1: SPI数据 */ #define OLED_RST_PIN GPIO_Pin_4 /* RES: 复位 */ #define OLED_DC_PIN GPIO_Pin_3 /* DC: 命令/数据 */ #define OLED_CS_PIN GPIO_Pin_2 /* CS: 片选 */ /* GPIO 操作宏 */ #define OLED_CLK_H() GPIO_SetBits(OLED_GPIO, OLED_CLK_PIN) #define OLED_CLK_L() GPIO_ResetBits(OLED_GPIO, OLED_CLK_PIN) #define OLED_MOSI_H() GPIO_SetBits(OLED_GPIO, OLED_MOSI_PIN) #define OLED_MOSI_L() GPIO_ResetBits(OLED_GPIO, OLED_MOSI_PIN) #define OLED_RST_H() GPIO_SetBits(OLED_GPIO, OLED_RST_PIN) #define OLED_RST_L() GPIO_ResetBits(OLED_GPIO, OLED_RST_PIN) #define OLED_DC_H() GPIO_SetBits(OLED_GPIO, OLED_DC_PIN) #define OLED_DC_L() GPIO_ResetBits(OLED_GPIO, OLED_DC_PIN) #define OLED_CS_H() GPIO_SetBits(OLED_GPIO, OLED_CS_PIN) #define OLED_CS_L() GPIO_ResetBits(OLED_GPIO, OLED_CS_PIN) /* ------------------------------------------------------------------ */ /* 6x8 ASCII 字模0x20开始 */ /* ------------------------------------------------------------------ */ static const uint8_t ASCII6x8[][6] { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // sp { 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 }, // ! { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 }, // { 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // # { 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // $ { 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 }, // % { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 }, // { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 }, // { 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 }, // ( { 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 }, // ) { 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 }, // * { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // { 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 }, // , { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 }, // - { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // . { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 }, // / { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0 { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1 { 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2 { 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3 { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4 { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5 { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6 { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7 { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8 { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9 { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 }, // : { 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 }, // ; { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 }, // { 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 }, // { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 }, // ? { 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E }, // { 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C }, // A { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F { 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H { 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R { 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 }, // S { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 }, // X { 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z }; /* ------------------------------------------------------------------ */ /* 延时函数 */ /* ------------------------------------------------------------------ */ static void delay_us(uint32_t us) { us * 8; while (us--); } static void delay_ms(uint32_t ms) { while (ms--) delay_us(1000); } /* ------------------------------------------------------------------ */ /* GPIO 初始化 */ /* ------------------------------------------------------------------ */ static void oled_gpio_init(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin OLED_CLK_PIN | OLED_MOSI_PIN | OLED_RST_PIN | OLED_DC_PIN | OLED_CS_PIN; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; /* 推挽输出 */ GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(OLED_GPIO, GPIO_InitStruct); /* 初始状态 */ OLED_CS_H(); OLED_CLK_H(); OLED_RST_H(); OLED_DC_H(); } /* ------------------------------------------------------------------ */ /* 软件模拟 SPI 发送一字节MSB firstMode3CPOL1 CPHA1 */ /* ------------------------------------------------------------------ */ static void spi_write_byte(uint8_t byte) { uint8_t i; for (i 0; i 8; i) { OLED_CLK_L(); if (byte 0x80) OLED_MOSI_H(); else OLED_MOSI_L(); byte 1; OLED_CLK_H(); } } /* ------------------------------------------------------------------ */ /* 发送命令 / 数据 */ /* ------------------------------------------------------------------ */ static void oled_write_cmd(uint8_t cmd) { OLED_CS_L(); OLED_DC_L(); /* DC0: 命令 */ spi_write_byte(cmd); OLED_CS_H(); } static void oled_write_data(uint8_t data) { OLED_CS_L(); OLED_DC_H(); /* DC1: 数据 */ spi_write_byte(data); OLED_CS_H(); } /* ------------------------------------------------------------------ */ /* 硬件复位 */ /* ------------------------------------------------------------------ */ static void oled_reset(void) { OLED_RST_H(); delay_ms(1); OLED_RST_L(); delay_ms(20); OLED_RST_H(); delay_ms(20); } /* ------------------------------------------------------------------ */ /* SH1106 初始化序列 */ /* ------------------------------------------------------------------ */ static void oled_init(void) { oled_reset(); oled_write_cmd(0xAE); /* 关显示 */ oled_write_cmd(0x02); /* 列低4位2SH1106偏移 */ oled_write_cmd(0x10); /* 列高4位0 */ oled_write_cmd(0x40); /* 起始行0 */ oled_write_cmd(0x81); /* 对比度 */ oled_write_cmd(0xCF); oled_write_cmd(0xA1); /* 列正向 */ oled_write_cmd(0xC8); /* 行正向 */ oled_write_cmd(0xA6); /* 正常显示 */ oled_write_cmd(0xA8); /* 多路复用率 */ oled_write_cmd(0x3F); /* 64行 */ oled_write_cmd(0xD3); /* 显示偏移 */ oled_write_cmd(0x00); oled_write_cmd(0xD5); /* 时钟分频 */ oled_write_cmd(0x80); oled_write_cmd(0xD9); /* 预充电 */ oled_write_cmd(0xF1); oled_write_cmd(0xDA); /* COM引脚配置 */ oled_write_cmd(0x12); oled_write_cmd(0xDB); /* VCOMH */ oled_write_cmd(0x40); oled_write_cmd(0x8D); /* 电荷泵 */ oled_write_cmd(0x14); /* 开启 */ oled_write_cmd(0xA4); /* 输出跟随RAM */ oled_write_cmd(0xA6); /* 正常显示 */ oled_write_cmd(0xAF); /* 开显示 */ } /* ------------------------------------------------------------------ */ /* 设置坐标SH1106 列偏移2 */ /* ------------------------------------------------------------------ */ static void oled_pos(uint8_t x, uint8_t y) { oled_write_cmd(0xB0 y); /* 页地址 */ oled_write_cmd(((x 0xF0) 4) | 0x10); /* 列高4位 */ oled_write_cmd((x 0x0F) | 0x02); /* 列低4位偏移2 */ } /* ------------------------------------------------------------------ */ /* 清屏 */ /* ------------------------------------------------------------------ */ static void oled_clear(void) { uint8_t x, y; for (y 0; y 8; y) { oled_write_cmd(0xB0 y); oled_write_cmd(0x02); oled_write_cmd(0x10); for (x 0; x 128; x) oled_write_data(0x00); } } /* ------------------------------------------------------------------ */ /* 全亮 */ /* ------------------------------------------------------------------ */ static void oled_full_bright(void) { uint8_t x, y; for (y 0; y 8; y) { oled_write_cmd(0xB0 y); oled_write_cmd(0x02); oled_write_cmd(0x10); for (x 0; x 128; x) oled_write_data(0xFF); } } /* ------------------------------------------------------------------ */ /* 显示字符串6x8 */ /* ------------------------------------------------------------------ */ static void oled_show_string(uint8_t x, uint8_t y, const char *str) { uint8_t i, k; while (*str) { if (*str 0x20 || *str 0x7A) { str; continue; } k *str - 0x20; if (x 121) { x 0; y; } oled_pos(x, y); for (i 0; i 6; i) oled_write_data(ASCII6x8[k][i]); x 6; str; } } /* ------------------------------------------------------------------ */ /* main */ /* ------------------------------------------------------------------ */ int main(void) { /* 系统时钟初始化使用默认72MHz */ SystemInit(); /* GPIO 初始化 */ oled_gpio_init(); delay_ms(100); /* OLED 初始化 */ oled_init(); /* 步骤1: 全亮测试 - 验证硬件通信是否正常 */ oled_full_bright(); delay_ms(2000); /* 步骤2: 清屏 */ oled_clear(); delay_ms(1000); /* 步骤3: 显示文字 */ oled_show_string(0, 0, HELLO STM32); oled_show_string(0, 2, SH1106 TEST); oled_show_string(0, 4, SPI OK!); oled_show_string(0, 6, 128x64 OLED); delay_ms(3000); /* 步骤4: 循环滚动显示 */ while (1) { oled_clear(); oled_show_string(0, 0, Hardware Test); oled_show_string(0, 2, CLK - PA5); oled_show_string(0, 4, MOSI - PA7); oled_show_string(0, 6, CS - PA2); delay_ms(2000); oled_clear(); oled_show_string(0, 0, Hardware Test); oled_show_string(0, 2, DC - PA3); oled_show_string(0, 4, RES - PA4); oled_show_string(0, 6, VCC - 3.3V); delay_ms(2000); /* 全亮闪烁 */ oled_full_bright(); delay_ms(500); oled_clear(); delay_ms(500); oled_full_bright(); delay_ms(500); oled_clear(); delay_ms(500); } }参考资料Linux手写FrameBuffer任意引脚驱动spi屏幕https://blog.csdn.net/plmm__/article/details/145193029?spm1001.2014.3001.5502STM32模块例程介绍包含SPIhttps://doc.embedfire.com/module/module_tutorial/zh/latest/README.html野火RK3568的SPI模块资料https://doc.embedfire.com/linux/rk356x/driver/zh/latest/linux_driver/subsystem_spi_subsystem.html#rk3588s驱动oledhttps://zhuanlan.zhihu.com/p/1906008511390935030NXP i.MX 系列芯片驱动OLEDhttps://www.heiweilu.top/index.php/linuxqudongmokuaiqioled-spi/第 12 篇 RK 平台安卓驱动实战 5SPI 设备驱动开发以 SPI 屏 / Flash 为例https://blog.csdn.net/alspd_zhangpan/article/details/159164992?spm1001.2014.3001.5502

相关新闻

从企微机器人到智能体操作系统:OpenClaw架构解析与实战部署

从企微机器人到智能体操作系统:OpenClaw架构解析与实战部署

1. 项目概述:从“企微机器人”到“智能体操作系统”的认知跃迁 第一次听说“OpenClaw”这个名字,很多人会下意识地把它归类为又一个“IM机器人”或者“AgentCLI工具”。毕竟,在企微、钉钉里挂个机器人自动回复消息,或者通过命令行…

2026/10/28 22:37:04 阅读更多 →
从Manus看AI Agent云端沙箱:技术演进与底层实现解析

从Manus看AI Agent云端沙箱:技术演进与底层实现解析

近期,Manus的爆火与Meta收购事件的波折,让AI Agent赛道再次成为焦点。作为全球领先的通用AI Agent,Manus最令人惊叹的能力,莫过于能像人类一样自主操作"云端电脑"——写代码、建网站、做调研、自动化办公,全…

2026/10/28 6:37:01 阅读更多 →
Maxwell-Pro 测试系统完整实现

Maxwell-Pro 测试系统完整实现

Maxwell-Pro 测试系统完整实现 我将为您构建完整的 Maxwell-Pro 测试系统,基于 WPF + Prism + CommunityToolkit.Mvvm 架构。 项目结构 MaxWell.Solution/ ├── MaxWell.Common/ # 基础工具、模型、接口 ├── MaxWell.Models/ # 实体模型 ├── M…

2026/10/28 8:19:08 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/28 23:48:14 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/28 7:28:00 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/28 9:02:11 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/28 22:10:30 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/28 9:00:54 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/28 7:29:02 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 16:58:40 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 9:01:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 19:25:09 阅读更多 →