diff --git a/CMakeLists.txt b/CMakeLists.txt index 229ec68..efdad1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,8 @@ pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(PicoIris src/PicoIris.cpp ) +add_executable(PicoIris src/PicoIris.cpp src/ov2640/camera.cpp src/ov2640/SCCB.cpp src/pio/i2c/pio_i2c.c) +#add_executable(PicoIris bus_scan.c) pico_set_program_name(PicoIris "PicoIris") pico_set_program_version(PicoIris "0.1") @@ -47,22 +48,21 @@ pico_generate_pio_header(PicoIris ${CMAKE_CURRENT_LIST_DIR}/src/pio/i2c/i2c.pio) pico_enable_stdio_uart(PicoIris 0) pico_enable_stdio_usb(PicoIris 1) -# Add the standard library to the build -target_link_libraries(PicoIris - pico_stdlib) - # Add the standard include files to the build target_include_directories(PicoIris PRIVATE ${CMAKE_CURRENT_LIST_DIR} src/ src/ov2640/ + src/pio/i2c/ ) # Add any user requested libraries target_link_libraries(PicoIris + pico_stdlib hardware_spi hardware_i2c hardware_dma + hardware_pwm hardware_pio hardware_interp hardware_timer diff --git a/bus_scan.c b/bus_scan.c new file mode 100644 index 0000000..f54b19d --- /dev/null +++ b/bus_scan.c @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Sweep through all 7-bit I2C addresses, to see if any slaves are present on +// the I2C bus. Print out a table that looks like this: +// +// I2C Bus Scan +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +// 00 . . . . . . . . . . . . . . . . +// 10 . . @ . . . . . . . . . . . . . +// 20 . . . . . . . . . . . . . . . . +// 30 . . . . @ . . . . . . . . . . . +// 40 . . . . . . . . . . . . . . . . +// 50 . . . . . . . . . . . . . . . . +// 60 . . . . . . . . . . . . . . . . +// 70 . . . . . . . . . . . . . . . . +// E.g. if addresses 0x12 and 0x34 were acknowledged. + +#include +#include "pico/stdlib.h" +#include "pico/binary_info.h" +#include "hardware/i2c.h" +#include "pico/stdlib.h" +#include "hardware/spi.h" +#include "hardware/i2c.h" +#include "hardware/dma.h" +#include "hardware/pio.h" +#include "hardware/interp.h" +#include "hardware/timer.h" +#include "hardware/pwm.h" +#include "hardware/clocks.h" +#include "hardware/uart.h" +// I2C reserves some addresses for special purposes. We exclude these from the scan. +// These are any addresses of the form 000 0xxx or 111 1xxx +bool reserved_addr(uint8_t addr) { + return (addr & 0x78) == 0 || (addr & 0x78) == 0x78; +} + +int main() { + // Enable UART so we can print status output + stdio_init_all(); +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) +#warning i2c/bus_scan example requires a board with I2C pins + puts("Default I2C pins were not defined"); +#else + // This example will use I2C0 on the default SDA and SCL pins (GP4, GP5 on a Pico) + gpio_set_function(3, GPIO_FUNC_PWM); + uint slice_num = pwm_gpio_to_slice_num(3); + // 6 cycles (0 to 5), 125 MHz / 6 = ~20.83 MHz wrap rate + pwm_set_wrap(slice_num, 5); + pwm_set_gpio_level(3, 3); + pwm_set_enabled(slice_num, true); + sleep_ms(1000); + + i2c_init(i2c_default, 100 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); + // Make the I2C pins available to picotool + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); + + printf("\nI2C Bus Scan\n"); + printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n"); + + for (int addr = 0; addr < (1 << 7); ++addr) { + if (addr % 16 == 0) { + printf("%02x ", addr); + } + + // Perform a 1-byte dummy read from the probe address. If a slave + // acknowledges this address, the function returns the number of bytes + // transferred. If the address byte is ignored, the function returns + // -1. + + // Skip over any reserved addresses. + int ret; + uint8_t rxdata; + if (reserved_addr(addr)) + ret = PICO_ERROR_GENERIC; + else + ret = i2c_read_blocking(i2c_default, addr, &rxdata, 1, false); + + printf(ret < 0 ? "." : "@"); + printf(addr % 16 == 15 ? "\n" : " "); + } + printf("Done.\n"); + return 0; +#endif +} \ No newline at end of file diff --git a/src/PicoIris.cpp b/src/PicoIris.cpp index 15527d9..e3fb0ca 100644 --- a/src/PicoIris.cpp +++ b/src/PicoIris.cpp @@ -18,13 +18,13 @@ camera_config_t left_eye_config = { .pin_pwdn = -1, /*!< GPIO pin for camera power down line */ //? Also called PWDN, or set to -1 and tie to GND .pin_reset = -1, /*!< GPIO pin for camera reset line */ //? Cam reset, or set to -1 and tie to 3.3V - .pin_xclk = 0, /*!< GPIO pin for camera XCLK line */ //? in theory could be shared or perhaps ommited? - .pin_sccb_sda = 0, /*!< GPIO pin for camera SDA line */ - .pin_sccb_scl = 0, /*!< GPIO pin for camera SCL line */ - .pin_data_base = 0, /*!< this pin + 7 consecutive will be used D0-D7 */ - .pin_vsync = 0, /*!< GPIO pin for camera VSYNC line */ - .pin_href = 0, /*!< GPIO pin for camera HREF line */ - .pin_pclk = 0, /*!< GPIO pin for camera PCLK line */ + .pin_xclk = 3, /*!< GPIO pin for camera XCLK line */ //? in theory could be shared or perhaps ommited? + .pin_sccb_sda = 4, /*!< GPIO pin for camera SDA line */ + .pin_sccb_scl = 5, /*!< GPIO pin for camera SCL line */ + .pin_data_base = 6, /*!< this pin + 7 consecutive will be used D0-D7 */ + .pin_vsync = 16, /*!< GPIO pin for camera VSYNC line */ + .pin_href = 15, /*!< GPIO pin for camera HREF line */ + .pin_pclk = 14, /*!< GPIO pin for camera PCLK line */ .xclk_freq_hz = 0, /*!< Frequency of XCLK signal, in Hz. */ //! Figure out the highest it can go to .sccb_ctrl = 0, /* Select i2c controller ctrl: 0 - i2c0, 1 - i2c1, 2 - pio0, 3 - pio1, 4 - pio2 */ }; @@ -33,11 +33,18 @@ int main() { stdio_init_all(); + sleep_ms(4000); + printf("Hello World!"); + + printf("construct OV2640!"); OV2640 left_cam = OV2640(left_eye_config); + printf("begin OV2640!"); left_cam.begin(DMA_IRQ_0); + while (true) { - printf("Hello, world!\n"); - sleep_ms(1000); + //printf("Hello, world!\n"); + //sleep_ms(1000); + tight_loop_contents(); } } diff --git a/src/ov2640/SCCB.cpp b/src/ov2640/SCCB.cpp index 0668f73..a6ea7a0 100644 --- a/src/ov2640/SCCB.cpp +++ b/src/ov2640/SCCB.cpp @@ -59,8 +59,8 @@ int SCCB::readRegister(uint8_t reg) i2c_write_blocking(this->i2cc, OV2640_ADDR, ®, 1, true); // TODO: check if true or false is correct here i2c_read_blocking(this->i2cc, OV2640_ADDR, buf, 1, false); // false - finished with bus }else { - pio_i2c_write_blocking(this->pio, this->sm, OV2640_ADDR, ®, 1); - pio_i2c_read_blocking(this->pio, this->sm, OV2640_ADDR, buf, 1); + //pio_i2c_write_blocking(this->pio, this->sm, OV2640_ADDR, ®, 1); + //pio_i2c_read_blocking(this->pio, this->sm, OV2640_ADDR, buf, 1); } return buf[0]; } @@ -74,7 +74,7 @@ void SCCB::writeRegister(uint8_t reg, uint8_t value) if (this->ctrl < 2) { i2c_write_blocking(this->i2cc, OV2640_ADDR, buf, 2, false); }else { - pio_i2c_write_blocking(this->pio, this->sm, OV2640_ADDR, buf, 2); + //pio_i2c_write_blocking(this->pio, this->sm, OV2640_ADDR, buf, 2); } } @@ -84,6 +84,6 @@ void SCCB::writeList(const register_val_t *cfg, for (int i = 0; i < len; i++) { writeRegister(cfg[i].reg, cfg[i].value); - sleep_ms(1000); // Some cams require, else lockup on init + sleep_ms(10); // Some cams require, else lockup on init } } \ No newline at end of file diff --git a/src/ov2640/SCCB.hpp b/src/ov2640/SCCB.hpp index 1cc8c4a..b797663 100644 --- a/src/ov2640/SCCB.hpp +++ b/src/ov2640/SCCB.hpp @@ -1,3 +1,8 @@ +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "hardware/pio.h" + typedef struct { uint8_t reg; ///< Register address uint8_t value; ///< Value to store @@ -17,8 +22,8 @@ public: private: uint8_t sda; uint8_t scl; - uint ctrl; + int ctrl; i2c_inst_t* i2cc; pio_hw_t* pio; - uint sm; + int sm; }; \ No newline at end of file diff --git a/src/ov2640/camera.cpp b/src/ov2640/camera.cpp index 8c59aaf..e134e90 100644 --- a/src/ov2640/camera.cpp +++ b/src/ov2640/camera.cpp @@ -1,5 +1,7 @@ -#include #include +#include + +#include #include "pico/stdlib.h" #include "../../hardware_dma/include/hardware/dma.h" @@ -281,7 +283,7 @@ register_val_t OV2640_init[] = { {OV2640_REG0_R_DVP_SP, 0x02}, // Manual DVP PCLK setting {OV2640_REG0_RESET, 0x00}}; // Go // TODO: figure out how to set to JPEG and 260x260 -/*OV2640_qqvga[] = +register_val_t OV2640_qqvga[] = {// Configure OV2640 for QQVGA output {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 {OV2640_REG0_RESET, OV2640_RESET_DVP}, @@ -300,16 +302,18 @@ register_val_t OV2640_init[] = { {OV2640_REG0_ZMOH, 0x1E}, // OUTH low bits {OV2640_REG0_ZMHH, 0x00}, // OUTW/H high bits {OV2640_REG0_R_DVP_SP, 0x08}, // Manual DVP PCLK setting - {OV2640_REG0_RESET, 0x00}}, // Go -OV2640_rgb[] = {{OV2640_REG_RA_DLMT, - OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {OV2640_REG0_RESET, 0x00} + }; // Go +register_val_t OV2640_rgb[] = { + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 {OV2640_REG0_RESET, OV2640_RESET_DVP}, {OV2640_REG0_IMAGE_MODE, OV2640_IMAGE_MODE_DVP_RGB565 | OV2640_IMAGE_MODE_BYTE_SWAP}, {0xD7, 0x03}, // Mystery init values {0xE1, 0x77}, // seen in other examples - {OV2640_REG0_RESET, 0x00}}, // Go -OV2640_yuv[] = { + {OV2640_REG0_RESET, 0x00} + }; // Go +register_val_t OV2640_yuv[] = { {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 {OV2640_REG0_RESET, OV2640_RESET_DVP}, {OV2640_REG0_IMAGE_MODE, @@ -317,15 +321,34 @@ OV2640_yuv[] = { {0xD7, 0x01}, // Mystery init values {0xE1, 0x67}, // seen in other examples {OV2640_REG0_RESET, 0x00} -}; // Go*/ +}; // Go -uint8_t image_buf[3][13 * 1024]; +uint8_t image_buf[352*288*2]; sm_dma_config_t gpios[48]; +int dma_channel; +int sm; OV2640::OV2640(camera_config_t config) { this->config = config; - this->fb = &image_buf; + //this->fb = &image_buf; +} + +// Pin interrupt on VSYNC calls this to start DMA transfer (unless suspended). +static void vsync_irq(uint gpio, uint32_t events) +{ + pio_sm_clear_fifos(pio2, sm); + dma_channel_start(dma_channel); +} + +static void dma_finish_irq() +{ + // Channel MUST be reconfigured each time (to reset the dest address). + dma_channel_set_write_addr(dma_channel, + (uint8_t *)(image_buf), false); + dma_hw->ints0 = 1u << dma_channel; // Clear IRQ + uint32_t data = pio2->rxf[sm]; + fwrite(&data, 1, 32, stdout); } int OV2640::begin(int dma_irq) @@ -341,26 +364,36 @@ int OV2640::begin(int dma_irq) pwm_set_enabled(slice_num, true); // Init SCCB + printf("Init SCCB\n"); this->sccb = SCCB(this->config.pin_sccb_sda, this->config.pin_sccb_scl); this->sccb.begin(this->config.sccb_ctrl); + printf("Inited SCCB\n"); // ENABLE AND/OR RESET CAMERA -------------------------------------------- // Unsure of camera startup time from beginning of input clock. // Let's guess it's similar to tS:REG (300 ms) from datasheet. - sleep_ms(300000); + sleep_ms(300); // Perform a soft reset (we dont wanna waste GPIO on wiring in reset pins) this->sccb.writeRegister(OV2640_REG_RA_DLMT, OV2640_RA_DLMT_SENSOR); // Bank select 1 this->sccb.writeRegister(OV2640_REG1_COM7, OV2640_COM7_SRST); // System reset - sleep_ms(1); // Datasheet: tS:RESET = 1 ms + sleep_ms(300); // Datasheet: tS:RESET = 1 ms // Init main camera settings + printf("Write list of init\n"); this->sccb.writeList(OV2640_init, sizeof OV2640_init / sizeof OV2640_init[0]); + printf("Write list of qqvga\n"); + this->sccb.writeList(OV2640_qqvga, sizeof OV2640_qqvga / sizeof OV2640_qqvga[0]); + printf("Write list of rgb\n"); + this->sccb.writeList(OV2640_rgb, sizeof OV2640_rgb / sizeof OV2640_rgb[0]); + + printf("Gpio init\n"); gpio_init(this->config.pin_pclk); gpio_set_dir(this->config.pin_pclk, GPIO_IN); gpio_init(this->config.pin_vsync); gpio_set_dir(this->config.pin_vsync, GPIO_IN); + gpio_pull_down(this->config.pin_vsync); gpio_init(this->config.pin_href); gpio_set_dir(this->config.pin_href, GPIO_IN); for (uint8_t i = 0; i < 8; i++) @@ -368,19 +401,21 @@ int OV2640::begin(int dma_irq) gpio_init(this->config.pin_data_base + i); gpio_set_dir(this->config.pin_data_base + i, GPIO_IN); } + printf("Gpio init done\n"); // PIO periph to use is currently specified by the user in the arch struct, // but I suppose this could be written to use whatever PIO has resources. // Mask the GPIO pin used PCLK into the PIO opcodes -- see notes at top // TODO: check if this is correct (code made for rp2040 not rp2350 so it might differ) + printf("PIO init\n"); capture_pio_opcodes[0] |= (this->config.pin_href & 31); capture_pio_opcodes[1] |= (this->config.pin_pclk & 31); capture_pio_opcodes[3] |= (this->config.pin_pclk & 31); uint offset = pio_add_program(pio2, &cap_pio_program); this->sm = pio_claim_unused_sm(pio2, true); // 0-3 - + sm = this->sm; pio_sm_set_consecutive_pindirs(pio2, this->sm, this->config.pin_data_base, 8, false); pio_sm_config c = pio_get_default_sm_config(); @@ -393,11 +428,12 @@ int OV2640::begin(int dma_irq) pio_sm_init(pio2, this->sm, offset, &c); pio_sm_set_enabled(pio2, this->sm, true); + printf("PIO init done\n"); // SET UP DMA ------------------------------------------------------------ - + printf("DMA init\n"); this->dma_channel = dma_claim_unused_channel(false); // don't panic - + dma_channel = this->dma_channel; this->dma_config = dma_channel_get_default_config(this->dma_channel); channel_config_set_transfer_data_size(&this->dma_config, DMA_SIZE_16); channel_config_set_read_increment(&this->dma_config, false); @@ -416,7 +452,7 @@ int OV2640::begin(int dma_irq) // Set up end-of-DMA interrupt dma_channel_set_irq0_enabled(this->dma_channel, true); - + printf("DMA init done\n"); //? setup diffrent handlers for each cam??? //? check with a button or smth if this fires all events or only one //? cuz then I would be able to do dma_finish_irq_cam1, dma_finish_irq_cam2, dma_finish_irq_cam3 @@ -428,29 +464,17 @@ int OV2640::begin(int dma_irq) //? This also could be redone with hardcoded values gpio_set_irq_enabled_with_callback(this->config.pin_vsync, GPIO_IRQ_EDGE_RISE, true, &vsync_irq); - + printf("IRQ init done\n"); return 0; } -// Pin interrupt on VSYNC calls this to start DMA transfer (unless suspended). -static void vsync_irq(uint gpio, uint32_t events) -{ - pio_sm_clear_fifos(pio2, archptr->sm); - dma_channel_start(archptr->dma_channel); -} -static void dma_finish_irq() -{ - // Channel MUST be reconfigured each time (to reset the dest address). - dma_channel_set_write_addr(archptr->dma_channel, - (uint8_t *)(image_buf), false); - dma_hw->ints0 = 1u << archptr->dma_channel; // Clear IRQ -} //! https://forums.raspberrypi.com/viewtopic.php?t=339299 // TODO: change to switches and experiment with state machines, check if I can kinda reserve dma's so 0-3 would indicate state machines 0-3 /* Read the flags +pio0_hw->irq; uint32_t flags = read_register(PIO0_BASE, PIO_IRQ_OFFSET) // Check the flags to determine which state machine made the request if (flags & 0b0001) { handle_sm_0(); } diff --git a/src/ov2640/camera.hpp b/src/ov2640/camera.hpp index 2bbfda4..316440e 100644 --- a/src/ov2640/camera.hpp +++ b/src/ov2640/camera.hpp @@ -1,3 +1,6 @@ +#include +#include "pico/stdlib.h" +#include "../../hardware_dma/include/hardware/dma.h" // TODO: Add XCLK settings typedef struct { int pin_pwdn; /*!< GPIO pin for camera power down line */ //? Also called PWDN, or set to -1 and tie to GND @@ -14,8 +17,8 @@ typedef struct { } camera_config_t; typedef struct { - uint sm; - uint dma_channel; + int sm; + int dma_channel; } sm_dma_config_t; @@ -29,8 +32,8 @@ private: camera_config_t config; SCCB sccb; uint8_t* (*fb)[3]; - uint sm; - uint dma_channel; + int sm; + int dma_channel; dma_channel_config dma_config; }; diff --git a/src/pio/i2c/pio_i2c.c b/src/pio/i2c/pio_i2c.c index 7c342d0..8997c24 100644 --- a/src/pio/i2c/pio_i2c.c +++ b/src/pio/i2c/pio_i2c.c @@ -3,8 +3,8 @@ * * SPDX-License-Identifier: BSD-3-Clause */ - - #include "pio_i2c.hpp" +#include "pio_i2c.h" + #include "i2c.pio.h" const int PIO_I2C_ICOUNT_LSB = 10; const int PIO_I2C_FINAL_LSB = 9;