‌树莓派Pico‌的pio的iic的SSD3306

pico-sdk-api文档

rp2040-datasheet.pdf

getting-started-with-pico.pdf

树莓派 Pico 之可编程 IO(PIO)

CMakeLists.txt

bash 复制代码
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(pio_squarewave C CXX ASM)
pico_sdk_init()

add_executable(pio_squarewave
        main.c
        pio_i2c.c
        iic.pio
)
pico_generate_pio_header(pio_squarewave ${CMAKE_CURRENT_LIST_DIR}/iic.pio)
target_link_libraries(pio_squarewave pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_squarewave)

iic.pio

c 复制代码
;
; 版权 (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;

.program i2c
.side_set 1 opt pindirs

;===============================
;  TX FIFO 数据格式(16 bit)
;===============================
; | 15:10 |  9   | 8:1  | 0   |
; | Instr | Final | Data | NAK |
;
; Instr > 0 :表示此 FIFO 项为"指令计数",无数据字节,
;             接下来的 Instr + 1 个 FIFO 项将被解析为指令序列。
;
; Instr = 0 :表示此 FIFO 项为"数据字节",需移出 Data[7:0] 并处理 ACK。
;
; Final = 1 :表示这是本次传输的最后一个字节,若收到 NAK 不会中断。
; Final = 0 :收到 NAK 则触发 IRQ 并暂停 SM。
;
; 自动拉取(autopull)需启用,阈值 16 bit。
; 自动推送(autopush)需启用,阈值 8 bit。
; 主机应使用半字(16bit)写入 TX FIFO,使 OSR 立即获得数据。
;
;===============================
;  引脚映射关系
;===============================
; SDA = 输入引脚 0, 输出 0, set pin 0, jmp pin
; SCL = 输入引脚 1 (用于 clock stretching)
; side-set 引脚 0 = SCL
; OUT 输出引脚 0 = SDA
;
; 等待(wait pin)要求:SCL 必须为 SDA + 1
;
; 注意:系统层需将 OE(输出使能)反相!
; 因 I²C 总线要求:OE=0 时驱动低电平,OE=1 时释放为上拉。
;

do_nack:
    jmp y-- entry_point        ; 若 y>0 表示允许 NAK,则继续执行
    irq wait 0 rel             ; 否则立即触发 IRQ,请求上层软件处理

;===============================
;  发送/接收一个完整字节(8bit + ACK)
;===============================
do_byte:
    set x, 7                   ; X = 7,表示需要循环 8 次(8 bit)
bitloop:
    out pindirs, 1         [7] ; 输出数据位(若是读操作则为全1表示释放)
    nop             side 1 [2] ; SCL 拉高(上升沿)
    wait 1 pin, 1          [4] ; 等待从机(支持 clock stretching)
    in pins, 1             [7] ; 在 SCL 高电平中间采样 SDA
    jmp x-- bitloop side 0 [7] ; SCL 拉低(下降沿),继续下一位

    ;===============
    ; 处理 ACK/NAK
    ;===============
    out pindirs, 1         [7] ; 读操作时,此处发送 ACK
    nop             side 1 [7] ; SCL 拉高
    wait 1 pin, 1          [7] ; 允许从机拉住 SCL(时钟延展)
    jmp pin do_nack side 0 [2] ; 若 SDA 高=NAK → 跳转 do_nack,否则继续

;=========================================
;  主执行入口
;=========================================
public entry_point:
.wrap_target
    out x, 6                   ; 从 OSR 取出 Instr(高 6 bits)
    out y, 1                   ; 取出 Final 位(是否忽略 NAK)
    jmp !x do_byte             ; 若 Instr = 0 → 处理数据字节
    out null, 32               ; Instr > 0 时,丢弃 OSR 其余数据位
do_exec:
    out exec, 16               ; 执行 1 条指令(从 FIFO 获取)
    jmp x-- do_exec            ; 循环 Instr + 1 次
.wrap


% c-sdk {

#include "hardware/clocks.h"
#include "hardware/gpio.h"


//=======================================================
//  初始化 PIO I²C 状态机
//  参数:
//      pio      - PIO 控制器
//      sm       - 状态机编号
//      offset   - 程序偏移地址
//      pin_sda  - SDA 引脚
//      pin_scl  - SCL 引脚(必须是 SDA+1)
//=======================================================
static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) {
    assert(pin_scl == pin_sda + 1);
    pio_sm_config c = i2c_program_get_default_config(offset);

    // 配置 IO 功能:OUT/SET/IN/SIDESET/JMP PIN
    sm_config_set_out_pins(&c, pin_sda, 1);
    sm_config_set_set_pins(&c, pin_sda, 1);
    sm_config_set_in_pins(&c, pin_sda);
    sm_config_set_sideset_pins(&c, pin_scl);
    sm_config_set_jmp_pin(&c, pin_sda);

    // 配置移位寄存器
    sm_config_set_out_shift(&c, false, true, 16); // 自动拉取 16bit
    sm_config_set_in_shift(&c, false, true, 8);   // 自动推送 8bit

    // 设置 I²C 时钟:系统时钟 / (32 * 100kHz)
    float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
    sm_config_set_clkdiv(&c, div);

    //----------------------------------------
    // 启动前避免"毛刺"影响 I²C 总线
    //----------------------------------------
    gpio_pull_up(pin_scl);
    gpio_pull_up(pin_sda);
    uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
    pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
    pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);

    pio_gpio_init(pio, pin_sda);
    gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT); // 反向 OE 以符合 I²C 开漏性质

    pio_gpio_init(pio, pin_scl);
    gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);

    pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);

    //----------------------------------------
    // 清除 IRQ 防止错误触发系统中断
    //----------------------------------------
    pio_set_irq0_source_enabled(pio, (enum pio_interrupt_source)((uint) pis_interrupt0 + sm), false);
    pio_set_irq1_source_enabled(pio, (enum pio_interrupt_source)((uint) pis_interrupt0 + sm), false);
    pio_interrupt_clear(pio, sm);

    //----------------------------------------
    // 初始化并启动 PIO 状态机
    //----------------------------------------
    pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
    pio_sm_set_enabled(pio, sm, true);
}

%}


.program set_scl_sda
.side_set 1 opt

;=======================================================
;  简单的 GPIO 控制指令表,用于生成 I²C 的
;  START、STOP、RESTART 等序列。
;  本程序不作为独立程序运行,只作为指令模板。
;=======================================================

    set pindirs, 0 side 0 [7] ; SCL=0, SDA=0
    set pindirs, 1 side 0 [7] ; SCL=0, SDA=1
    set pindirs, 0 side 1 [7] ; SCL=1, SDA=0
    set pindirs, 1 side 1 [7] ; SCL=1, SDA=1

% c-sdk {
// 指令表索引定义
enum {
    I2C_SC0_SD0 = 0,
    I2C_SC0_SD1,
    I2C_SC1_SD0,
    I2C_SC1_SD1
};
%}

diagram.json

json 复制代码
{
  "version": 1,
  "author": "wang minglie",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-pi-pico",
      "id": "pico",
      "top": -3.15,
      "left": 3.6,
      "attrs": { "builder": "pico-sdk" }
    },
    { "type": "wokwi-logic-analyzer", "id": "logic1", "top": -28.45, "left": 220.8, "attrs": {} },
    {
      "type": "board-ssd1306",
      "id": "oled1",
      "top": 108.74,
      "left": 134.63,
      "attrs": { "i2cAddress": "0x3c" }
    },
    { "type": "wokwi-vcc", "id": "vcc1", "top": -18.44, "left": 105.6, "attrs": {} },
    { "type": "wokwi-gnd", "id": "gnd1", "top": 153.6, "left": 105, "attrs": {} }
  ],
  "connections": [
    [ "pico:GP0", "$serialMonitor:RX", "", [] ],
    [ "pico:GP1", "$serialMonitor:TX", "", [] ],
    [ "oled1:SDA", "pico:GP2", "green", [ "v0" ] ],
    [ "oled1:SCL", "pico:GP3", "green", [ "v0" ] ],
    [ "logic1:D0", "oled1:SDA", "green", [ "h0" ] ],
    [ "logic1:D1", "oled1:SCL", "green", [ "h0" ] ],
    [ "gnd1:GND", "oled1:GND", "black", [ "v-57.6", "h-9.6" ] ],
    [ "oled1:VCC", "vcc1:VCC", "red", [ "v-38.4", "h-67.05" ] ]
  ],
  "dependencies": {}
}

wokwi.toml

xml 复制代码
[wokwi]
version = 1
firmware = "cmake-build-debug-pico/pio_squarewave.uf2"
elf = "cmake-build-debug-pico/pio_squarewave.elf"

main.c

c 复制代码
#include <stdio.h>
#include "pico/stdlib.h"
#include "pio_i2c.h"
#include <string.h>

#define PIN_SDA 2
#define PIN_SCL 3

// 假设已初始化 PIO I2C(pio0, sm0),以下是初始化指令序列
#define SSD3306_ADDR 0x3C
#define CMD_MODE 0x00  // 指令模式控制字节
#define DATA_MODE 0x40 // 数据模式控制字节

// SSD3306 屏幕参数(128x64 像素)
#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
#define PAGE_COUNT    8   // 64行 / 8行 per page = 8页


// 发送单字节指令
void ssd3306_send_cmd(uint8_t cmd) {
    uint sm = 0;
    uint8_t data[] = {CMD_MODE, cmd};
    pio_i2c_write_blocking(pio0, sm, SSD3306_ADDR, data, sizeof(data));
}

// 发送带参数的指令
void ssd3306_send_cmd_with_param(uint8_t cmd, uint8_t param) {
    uint sm = 0;
    uint8_t data[] = {CMD_MODE, cmd, param};
    pio_i2c_write_blocking(pio0, sm, SSD3306_ADDR, data, sizeof(data));
}

// 发送带2个参数的指令(如设置地址范围)
void ssd3306_send_cmd_with_2params(uint8_t cmd, uint8_t param1, uint8_t param2) {
    uint sm = 0;
    uint8_t data[] = {CMD_MODE, cmd, param1, param2};
    pio_i2c_write_blocking(pio0, sm, SSD3306_ADDR, data, sizeof(data));
}

// 初始化函数
void ssd3306_init() {
    ssd3306_send_cmd(0xAE);        // 关闭显示
    ssd3306_send_cmd_with_param(0xA8, 0x3F); // 多路复用比=64(128x64 屏)
    ssd3306_send_cmd_with_param(0xD3, 0x00); // 显示偏移=0
    ssd3306_send_cmd(0x40);        // 显示起始行=0
    ssd3306_send_cmd_with_param(0xA1, 0x00); // 段重映射=正常(0xA1 可改为 0xA0 翻转)
    ssd3306_send_cmd_with_param(0xDA, 0x12); // COM 引脚配置=0x12(128x64 屏)
    ssd3306_send_cmd_with_param(0x81, 0x7F); // 对比度=默认 0x7F(可调整 0x00~0xFF)
    ssd3306_send_cmd_with_param(0xA4, 0x00); // 正常显示(非全亮)
    ssd3306_send_cmd_with_param(0xA6, 0x00); // 正常极性(非反显)
    ssd3306_send_cmd_with_param(0xD5, 0x80); // 时钟分频=默认
    ssd3306_send_cmd_with_param(0xD9, 0x22); // 预充电周期=0x22(3.3V 供电)
    ssd3306_send_cmd_with_param(0xDB, 0x20); // VCOM 电压=0.77V
    ssd3306_send_cmd_with_param(0xAD, 0x8A); // 开启内置 DC-DC 转换器
    ssd3306_send_cmd_with_param(0x20, 0x02); // 显存寻址模式=页寻址(默认)
    ssd3306_send_cmd(0xAF);        // 开启显示
}

// 清屏(填充全黑)
void ssd3306_clear() {
    uint sm = 0;
    // 设置地址范围:全屏幕(列0~127,页0~7)
    ssd3306_send_cmd_with_2params(0x21, 0x00, SCREEN_WIDTH - 1);  // 列地址范围
    ssd3306_send_cmd_with_2params(0x22, 0x00, PAGE_COUNT - 1);    // 页地址范围

    // 填充 128*8=1024 字节的 0x00(全黑)
    uint8_t clear_buf[SCREEN_WIDTH + 1]; // 控制字节 + 128个数据字节
    clear_buf[0] = DATA_MODE;           // 数据模式控制字节
    memset(&clear_buf[1], 0x00, SCREEN_WIDTH); // 数据部分填充0

    for (int page = 0; page < PAGE_COUNT; page++) {
        pio_i2c_write_blocking(pio0, sm, SSD3306_ADDR, clear_buf, sizeof(clear_buf));
    }
}

/**
 * 画水平线
 * @param x1: 起始X坐标(0~127)
 * @param x2: 结束X坐标(0~127,需 ≥x1)
 * @param y: Y坐标(0~63)
 */
void ssd3306_draw_hline(uint8_t x1, uint8_t x2, uint8_t y) {
    if (x1 > x2 || y >= SCREEN_HEIGHT) return;

    // 计算当前Y坐标所在的页(page = y / 8)和页内偏移(bit = y % 8)
    uint8_t page = y / 8;
    uint8_t bit = y % 8;
    uint8_t pixel_data = 1 << bit; // 该Y坐标对应的像素位(1=点亮,0=熄灭)

    // 设置地址范围:列x1~x2,页page~page(仅操作当前页)
    ssd3306_send_cmd_with_2params(0x21, x1, x2);  // 列范围
    ssd3306_send_cmd_with_2params(0x22, page, page); // 页范围

    // 构造数据:控制字节 + (x2-x1+1)个像素数据
    uint8_t data_len = x2 - x1 + 1;
    uint8_t *line_buf = malloc(data_len + 1); // 动态分配缓冲区
    if (!line_buf) return;

    line_buf[0] = DATA_MODE; // 数据模式控制字节
    for (int i = 0; i < data_len; i++) {
        line_buf[i + 1] = pixel_data; // 每个X坐标都点亮对应Y位
    }

    // 写入显存
    pio_i2c_write_blocking(pio0, 0, SSD3306_ADDR, line_buf, data_len + 1);
    free(line_buf); // 释放缓冲区
}

/**
 * 画垂直线
 * @param x: X坐标(0~127)
 * @param y1: 起始Y坐标(0~63)
 * @param y2: 结束Y坐标(0~63,需 ≥y1)
 */
void ssd3306_draw_vline(uint8_t x, uint8_t y1, uint8_t y2) {
    if (x >= SCREEN_WIDTH || y1 > y2) return;

    // 遍历Y1到Y2对应的所有页
    for (uint8_t y = y1; y <= y2; y++) {
        uint8_t page = y / 8;
        uint8_t bit = y % 8;
        uint8_t pixel_data = 1 << bit;

        // 设置地址:列x~x,页page~page(仅操作当前X和页)
        ssd3306_send_cmd_with_2params(0x21, x, x);
        ssd3306_send_cmd_with_2params(0x22, page, page);

        // 写入单个像素数据
        uint8_t data[] = {DATA_MODE, pixel_data};
        pio_i2c_write_blocking(pio0, 0, SSD3306_ADDR, data, sizeof(data));
    }
}




int main() {
    // 初始化串口和PIO I2C
    stdio_init_all();
    sleep_ms(2000);  // Wait for UART connection (USB serial enumeration)

    PIO pio = pio0;
    uint sm = 0;
    uint offset = pio_add_program(pio, &i2c_program);
    i2c_program_init(pio, sm, offset, PIN_SDA, PIN_SCL);

    // 仅扫描 0x3C 地址
    printf("=== PIO I2C Single Address Scan ===\n");
    printf("Scanning target address: 0x%02X\n", SSD3306_ADDR);
    printf("SDA Pin: GPIO%d, SCL Pin: GPIO%d\n\n", PIN_SDA, PIN_SCL);

    // 检测 0x3C 地址(跳过保留地址判断,直接检测)
    int result = pio_i2c_read_blocking(pio, sm, SSD3306_ADDR, NULL, 0);

    // 输出检测结果(纯英文,无汉字)
    if (result >= 0) {
        printf("SUCCESS: Device found at 0x%02X\n", SSD3306_ADDR);
        printf("Status: I2C ACK received, bus communication normal\n");

        ssd3306_init();   // 初始化SSD3306
        ssd3306_clear();  // 清屏(避免乱码)
        // 画2条示例线
        ssd3306_draw_hline(10, 15, 32);  // 水平线:X1=10, X2=15, Y=32(屏幕中间的小短线)
        printf("Lines drawn successfully!\n");

    } else {
        printf("FAILED: No device found at 0x%02X\n", SSD3306_ADDR);
        printf("Possible reasons:\n");
        printf("  1. Device not powered on\n");
        printf("  2. Wrong wiring (SDA/SCL reversed?)\n");
        printf("  3. Missing 4.7K-10K pull-up resistors on SDA/SCL\n");
        printf("  4. Device actual address is not 0x%02X (e.g., 0x3D)\n", SSD3306_ADDR);
        printf("  5. GPIO pin definition mismatch with wiring\n");
    }

    // 循环保持程序运行
    while (true) {
        sleep_ms(1000);
    }

    return 0;
}

pio_i2c.c

c 复制代码
/**
 * Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "pio_i2c.h"

const int PIO_I2C_ICOUNT_LSB = 10;
const int PIO_I2C_FINAL_LSB  = 9;
const int PIO_I2C_DATA_LSB   = 1;
const int PIO_I2C_NAK_LSB    = 0;


bool pio_i2c_check_error(PIO pio, uint sm) {
    return pio_interrupt_get(pio, sm);
}

void pio_i2c_resume_after_error(PIO pio, uint sm) {
    pio_sm_drain_tx_fifo(pio, sm);
    pio_sm_exec(pio, sm, (pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >> PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
    pio_interrupt_clear(pio, sm);
}

void pio_i2c_rx_enable(PIO pio, uint sm, bool en) {
    if (en)
        hw_set_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
    else
        hw_clear_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
}

static inline void pio_i2c_put16(PIO pio, uint sm, uint16_t data) {
    while (pio_sm_is_tx_fifo_full(pio, sm))
        ;
    // some versions of GCC dislike this
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
    *(io_rw_16 *)&pio->txf[sm] = data;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}


// If I2C is ok, block and push data. Otherwise fall straight through.
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data) {
    while (pio_sm_is_tx_fifo_full(pio, sm))
        if (pio_i2c_check_error(pio, sm))
            return;
    if (pio_i2c_check_error(pio, sm))
        return;
    // some versions of GCC dislike this
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
    *(io_rw_16 *)&pio->txf[sm] = data;
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}

uint8_t pio_i2c_get(PIO pio, uint sm) {
    return (uint8_t)pio_sm_get(pio, sm);
}

void pio_i2c_start(PIO pio, uint sm) {
    pio_i2c_put_or_err(pio, sm, 1u << PIO_I2C_ICOUNT_LSB); // Escape code for 2 instruction sequence
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);    // We are already in idle state, just pull SDA low
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);    // Also pull clock low so we can present data
}

void pio_i2c_stop(PIO pio, uint sm) {
    pio_i2c_put_or_err(pio, sm, 2u << PIO_I2C_ICOUNT_LSB);
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);    // SDA is unknown; pull it down
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);    // Release clock
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]);    // Release SDA to return to idle state
};

void pio_i2c_repstart(PIO pio, uint sm) {
    pio_i2c_put_or_err(pio, sm, 3u << PIO_I2C_ICOUNT_LSB);
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD1]);
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]);
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);
}

static void pio_i2c_wait_idle(PIO pio, uint sm) {
    // Finished when TX runs dry or SM hits an IRQ
    pio->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
    while (!(pio->fdebug & 1u << (PIO_FDEBUG_TXSTALL_LSB + sm) || pio_i2c_check_error(pio, sm)))
        tight_loop_contents();
}

int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len) {
    int err = 0;
    pio_i2c_start(pio, sm);
    pio_i2c_rx_enable(pio, sm, false);
    pio_i2c_put16(pio, sm, (addr << 2) | 1u);
    while (len && !pio_i2c_check_error(pio, sm)) {
        if (!pio_sm_is_tx_fifo_full(pio, sm)) {
            --len;
            pio_i2c_put_or_err(pio, sm, (*txbuf++ << PIO_I2C_DATA_LSB) | ((len == 0) << PIO_I2C_FINAL_LSB) | 1u);
        }
    }
    pio_i2c_stop(pio, sm);
    pio_i2c_wait_idle(pio, sm);
    if (pio_i2c_check_error(pio, sm)) {
        err = -1;
        pio_i2c_resume_after_error(pio, sm);
        pio_i2c_stop(pio, sm);
    }
    return err;
}

int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len) {
    int err = 0;
    pio_i2c_start(pio, sm);
    pio_i2c_rx_enable(pio, sm, true);
    while (!pio_sm_is_rx_fifo_empty(pio, sm))
        (void)pio_i2c_get(pio, sm);
    pio_i2c_put16(pio, sm, (addr << 2) | 3u);
    uint32_t tx_remain = len; // Need to stuff 0xff bytes in to get clocks

    bool first = true;

    while ((tx_remain || len) && !pio_i2c_check_error(pio, sm)) {
        if (tx_remain && !pio_sm_is_tx_fifo_full(pio, sm)) {
            --tx_remain;
            pio_i2c_put16(pio, sm, (0xffu << 1) | (tx_remain ? 0 : (1u << PIO_I2C_FINAL_LSB) | (1u << PIO_I2C_NAK_LSB)));
        }
        if (!pio_sm_is_rx_fifo_empty(pio, sm)) {
            if (first) {
                // Ignore returned address byte
                (void)pio_i2c_get(pio, sm);
                first = false;
            }
            else {
                --len;
                *rxbuf++ = pio_i2c_get(pio, sm);
            }
        }
    }
    pio_i2c_stop(pio, sm);
    pio_i2c_wait_idle(pio, sm);
    if (pio_i2c_check_error(pio, sm)) {
        err = -1;
        pio_i2c_resume_after_error(pio, sm);
        pio_i2c_stop(pio, sm);
    }
    return err;
}

pio_i2c.h

c 复制代码
/**
 * Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#ifndef _PIO_I2C_H
#define _PIO_I2C_H

#include "iic.pio.h"

// ----------------------------------------------------------------------------
// Low-level functions

void pio_i2c_start(PIO pio, uint sm);
void pio_i2c_stop(PIO pio, uint sm);
void pio_i2c_repstart(PIO pio, uint sm);

bool pio_i2c_check_error(PIO pio, uint sm);
void pio_i2c_resume_after_error(PIO pio, uint sm);

// If I2C is ok, block and push data. Otherwise fall straight through.
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data);
uint8_t pio_i2c_get(PIO pio, uint sm);

// ----------------------------------------------------------------------------
// Transaction-level functions

int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len);
int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len);

#endif

iic.pio.h

c 复制代码
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //

#pragma once

#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif

// --- //
// i2c //
// --- //

#define i2c_wrap_target 12
#define i2c_wrap 17

#define i2c_offset_entry_point 12u

static const uint16_t i2c_program_instructions[] = {
    0x008c, //  0: jmp    y--, 12                    
    0xc030, //  1: irq    wait 0 rel                 
    0xe027, //  2: set    x, 7                       
    0x6781, //  3: out    pindirs, 1             [7] 
    0xba42, //  4: nop                    side 1 [2] 
    0x24a1, //  5: wait   1 pin, 1               [4] 
    0x4701, //  6: in     pins, 1                [7] 
    0x1743, //  7: jmp    x--, 3          side 0 [7] 
    0x6781, //  8: out    pindirs, 1             [7] 
    0xbf42, //  9: nop                    side 1 [7] 
    0x27a1, // 10: wait   1 pin, 1               [7] 
    0x12c0, // 11: jmp    pin, 0          side 0 [2] 
            //     .wrap_target
    0x6026, // 12: out    x, 6                       
    0x6041, // 13: out    y, 1                       
    0x0022, // 14: jmp    !x, 2                      
    0x6060, // 15: out    null, 32                   
    0x60f0, // 16: out    exec, 16                   
    0x0050, // 17: jmp    x--, 16                    
            //     .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program i2c_program = {
    .instructions = i2c_program_instructions,
    .length = 18,
    .origin = -1,
};

static inline pio_sm_config i2c_program_get_default_config(uint offset) {
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_wrap(&c, offset + i2c_wrap_target, offset + i2c_wrap);
    sm_config_set_sideset(&c, 2, true, true);
    return c;
}

#include "hardware/clocks.h"
#include "hardware/gpio.h"
//=======================================================
//  初始化 PIO I²C 状态机
//  参数:
//      pio      - PIO 控制器
//      sm       - 状态机编号
//      offset   - 程序偏移地址
//      pin_sda  - SDA 引脚
//      pin_scl  - SCL 引脚(必须是 SDA+1)
//=======================================================
static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) {
    assert(pin_scl == pin_sda + 1);
    pio_sm_config c = i2c_program_get_default_config(offset);
    // 配置 IO 功能:OUT/SET/IN/SIDESET/JMP PIN
    sm_config_set_out_pins(&c, pin_sda, 1);
    sm_config_set_set_pins(&c, pin_sda, 1);
    sm_config_set_in_pins(&c, pin_sda);
    sm_config_set_sideset_pins(&c, pin_scl);
    sm_config_set_jmp_pin(&c, pin_sda);
    // 配置移位寄存器
    sm_config_set_out_shift(&c, false, true, 16); // 自动拉取 16bit
    sm_config_set_in_shift(&c, false, true, 8);   // 自动推送 8bit
    // 设置 I²C 时钟:系统时钟 / (32 * 100kHz)
    float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
    sm_config_set_clkdiv(&c, div);
    //----------------------------------------
    // 启动前避免"毛刺"影响 I²C 总线
    //----------------------------------------
    gpio_pull_up(pin_scl);
    gpio_pull_up(pin_sda);
    uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
    pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
    pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);
    pio_gpio_init(pio, pin_sda);
    gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT); // 反向 OE 以符合 I²C 开漏性质
    pio_gpio_init(pio, pin_scl);
    gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);
    pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);
    //----------------------------------------
    // 清除 IRQ 防止错误触发系统中断
    //----------------------------------------
    pio_set_irq0_source_enabled(pio, (enum pio_interrupt_source)((uint) pis_interrupt0 + sm), false);
    pio_set_irq1_source_enabled(pio, (enum pio_interrupt_source)((uint) pis_interrupt0 + sm), false);
    pio_interrupt_clear(pio, sm);
    //----------------------------------------
    // 初始化并启动 PIO 状态机
    //----------------------------------------
    pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
    pio_sm_set_enabled(pio, sm, true);
}

#endif

// ----------- //
// set_scl_sda //
// ----------- //

#define set_scl_sda_wrap_target 0
#define set_scl_sda_wrap 3

static const uint16_t set_scl_sda_program_instructions[] = {
            //     .wrap_target
    0xf780, //  0: set    pindirs, 0      side 0 [7] 
    0xf781, //  1: set    pindirs, 1      side 0 [7] 
    0xff80, //  2: set    pindirs, 0      side 1 [7] 
    0xff81, //  3: set    pindirs, 1      side 1 [7] 
            //     .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program set_scl_sda_program = {
    .instructions = set_scl_sda_program_instructions,
    .length = 4,
    .origin = -1,
};

static inline pio_sm_config set_scl_sda_program_get_default_config(uint offset) {
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_wrap(&c, offset + set_scl_sda_wrap_target, offset + set_scl_sda_wrap);
    sm_config_set_sideset(&c, 2, true, false);
    return c;
}

// 指令表索引定义
enum {
    I2C_SC0_SD0 = 0,
    I2C_SC0_SD1,
    I2C_SC1_SD0,
    I2C_SC1_SD1
};

#endif
相关推荐
zephyr_zeng1 天前
CubeMX项目轻松导入Vscode+EIDE编译
c语言·ide·vscode·stm32·mcu·物联网·编辑器
minglie12 天前
‌树莓派Pico‌的pio的uart_rx
mcu
minglie13 天前
‌树莓派Pico‌的timer
mcu
minglie13 天前
‌树莓派Pico‌的pio的spi的flash
mcu
minglie13 天前
树莓派Pico‌的pio的dma
mcu
猫猫的小茶馆3 天前
【ARM】内核移植(编译)
linux·arm开发·stm32·单片机·嵌入式硬件·mcu·pcb工艺
minglie14 天前
‌树莓派Pico‌的pio的uart_tx
mcu
Bigan(安)4 天前
【奶茶Beta专项】【LVGL9.4源码分析】04-OS抽象层
linux·c语言·mcu·arm·unix
Bigan(安)4 天前
【奶茶Beta专项】【LVGL9.4源码分析】06-tick时间管理
linux·c语言·mcu·arm·unix