|
| 1 | +import rp2 |
| 2 | +from machine import Pin, I2C |
| 3 | +from ulab import numpy as np |
| 4 | +from time import sleep_us |
| 5 | +import time |
| 6 | + |
| 7 | +# Derived from: |
| 8 | +# https://github.com/openmv/openmv/blob/5acf5baf92b4314a549bdd068138e5df6cc0bac7/drivers/sensors/hm01b0.c |
| 9 | +class HM01B0_PIO(): |
| 10 | + # Derived from: |
| 11 | + # https://github.com/openmv/openmv/blob/5acf5baf92b4314a549bdd068138e5df6cc0bac7/drivers/sensors/hm01b0_regs.h |
| 12 | + |
| 13 | + # Read only registers |
| 14 | + MODEL_ID_H = 0x0000 |
| 15 | + MODEL_ID_L = 0x0001 |
| 16 | + FRAME_COUNT = 0x0005 |
| 17 | + PIXEL_ORDER = 0x0006 |
| 18 | + # Sensor mode control |
| 19 | + MODE_SELECT = 0x0100 |
| 20 | + IMG_ORIENTATION = 0x0101 |
| 21 | + SW_RESET = 0x0103 |
| 22 | + GRP_PARAM_HOLD = 0x0104 |
| 23 | + # Sensor exposure gain control |
| 24 | + INTEGRATION_H = 0x0202 |
| 25 | + INTEGRATION_L = 0x0203 |
| 26 | + ANALOG_GAIN = 0x0205 |
| 27 | + DIGITAL_GAIN_H = 0x020E |
| 28 | + DIGITAL_GAIN_L = 0x020F |
| 29 | + # Frame timing control |
| 30 | + FRAME_LEN_LINES_H = 0x0340 |
| 31 | + FRAME_LEN_LINES_L = 0x0341 |
| 32 | + LINE_LEN_PCK_H = 0x0342 |
| 33 | + LINE_LEN_PCK_L = 0x0343 |
| 34 | + # Binning mode control |
| 35 | + READOUT_X = 0x0383 |
| 36 | + READOUT_Y = 0x0387 |
| 37 | + BINNING_MODE = 0x0390 |
| 38 | + # Test pattern control |
| 39 | + TEST_PATTERN_MODE = 0x0601 |
| 40 | + # Black level control |
| 41 | + BLC_CFG = 0x1000 |
| 42 | + BLC_TGT = 0x1003 |
| 43 | + BLI_EN = 0x1006 |
| 44 | + BLC2_TGT = 0x1007 |
| 45 | + # Sensor reserved |
| 46 | + DPC_CTRL = 0x1008 |
| 47 | + SINGLE_THR_HOT = 0x100B |
| 48 | + SINGLE_THR_COLD = 0x100C |
| 49 | + # VSYNC,HSYNC and pixel shift register |
| 50 | + VSYNC_HSYNC_PIXEL_SHIFT_EN = 0x1012 |
| 51 | + # Automatic exposure gain control |
| 52 | + AE_CTRL = 0x2100 |
| 53 | + AE_TARGET_MEAN = 0x2101 |
| 54 | + AE_MIN_MEAN = 0x2102 |
| 55 | + CONVERGE_IN_TH = 0x2103 |
| 56 | + CONVERGE_OUT_TH = 0x2104 |
| 57 | + MAX_INTG_H = 0x2105 |
| 58 | + MAX_INTG_L = 0x2106 |
| 59 | + MIN_INTG = 0x2107 |
| 60 | + MAX_AGAIN_FULL = 0x2108 |
| 61 | + MAX_AGAIN_BIN2 = 0x2109 |
| 62 | + MIN_AGAIN = 0x210A |
| 63 | + MAX_DGAIN = 0x210B |
| 64 | + MIN_DGAIN = 0x210C |
| 65 | + DAMPING_FACTOR = 0x210D |
| 66 | + FS_CTRL = 0x210E |
| 67 | + FS_60HZ_H = 0x210F |
| 68 | + FS_60HZ_L = 0x2110 |
| 69 | + FS_50HZ_H = 0x2111 |
| 70 | + FS_50HZ_L = 0x2112 |
| 71 | + FS_HYST_TH = 0x2113 |
| 72 | + # Motion detection control |
| 73 | + MD_CTRL = 0x2150 |
| 74 | + I2C_CLEAR = 0x2153 |
| 75 | + WMEAN_DIFF_TH_H = 0x2155 |
| 76 | + WMEAN_DIFF_TH_M = 0x2156 |
| 77 | + WMEAN_DIFF_TH_L = 0x2157 |
| 78 | + MD_THH = 0x2158 |
| 79 | + MD_THM1 = 0x2159 |
| 80 | + MD_THM2 = 0x215A |
| 81 | + MD_THL = 0x215B |
| 82 | + STATISTIC_CTRL = 0x2000 |
| 83 | + MD_LROI_X_START_H = 0x2011 |
| 84 | + MD_LROI_X_START_L = 0x2012 |
| 85 | + MD_LROI_Y_START_H = 0x2013 |
| 86 | + MD_LROI_Y_START_L = 0x2014 |
| 87 | + MD_LROI_X_END_H = 0x2015 |
| 88 | + MD_LROI_X_END_L = 0x2016 |
| 89 | + MD_LROI_Y_END_H = 0x2017 |
| 90 | + MD_LROI_Y_END_L = 0x2018 |
| 91 | + MD_INTERRUPT = 0x2160 |
| 92 | + # Sensor timing control |
| 93 | + QVGA_WIN_EN = 0x3010 |
| 94 | + SIX_BIT_MODE_EN = 0x3011 |
| 95 | + PMU_AUTOSLEEP_FRAMECNT = 0x3020 |
| 96 | + ADVANCE_VSYNC = 0x3022 |
| 97 | + ADVANCE_HSYNC = 0x3023 |
| 98 | + EARLY_GAIN = 0x3035 |
| 99 | + # IO and clock control |
| 100 | + BIT_CONTROL = 0x3059 |
| 101 | + OSC_CLK_DIV = 0x3060 |
| 102 | + ANA_Register_11 = 0x3061 |
| 103 | + IO_DRIVE_STR = 0x3062 |
| 104 | + IO_DRIVE_STR2 = 0x3063 |
| 105 | + ANA_Register_14 = 0x3064 |
| 106 | + OUTPUT_PIN_STATUS_CONTROL = 0x3065 |
| 107 | + ANA_Register_17 = 0x3067 |
| 108 | + PCLK_POLARITY = 0x3068 |
| 109 | + |
| 110 | + # Useful values of Himax registers |
| 111 | + HIMAX_RESET = 0x01 |
| 112 | + HIMAX_MODE_STANDBY = 0x00 |
| 113 | + HIMAX_MODE_STREAMING = 0x01 # I2C triggered streaming enable |
| 114 | + HIMAX_MODE_STREAMING_NFRAMES = 0x03 # Output N frames |
| 115 | + HIMAX_MODE_STREAMING_TRIG = 0x05 # Hardware Trigger |
| 116 | + # HIMAX_SET_HMIRROR (r, x) ((r & 0xFE) | ((x & 1) << 0)) |
| 117 | + # HIMAX_SET_VMIRROR (r, x) ((r & 0xFD) | ((x & 1) << 1)) |
| 118 | + |
| 119 | + PCLK_RISING_EDGE = 0x00 |
| 120 | + PCLK_FALLING_EDGE = 0x01 |
| 121 | + AE_CTRL_ENABLE = 0x00 |
| 122 | + AE_CTRL_DISABLE = 0x01 |
| 123 | + |
| 124 | + HIMAX_BOOT_RETRY = 10 |
| 125 | + HIMAX_LINE_LEN_PCK_FULL = 0x178 |
| 126 | + HIMAX_FRAME_LENGTH_FULL = 0x109 |
| 127 | + |
| 128 | + HIMAX_LINE_LEN_PCK_QVGA = 0x178 |
| 129 | + HIMAX_FRAME_LENGTH_QVGA = 0x104 |
| 130 | + |
| 131 | + HIMAX_LINE_LEN_PCK_QQVGA = 0x178 |
| 132 | + HIMAX_FRAME_LENGTH_QQVGA = 0x084 |
| 133 | + |
| 134 | + INIT_COMMANDS = ( |
| 135 | + (0x3044, 0x0A), # Increase CDS time for settling |
| 136 | + (0x3045, 0x00), # Make symmetric for cds_tg and rst_tg |
| 137 | + (0x3047, 0x0A), # Increase CDS time for settling |
| 138 | + (0x3050, 0xC0), # Make negative offset up to 4x |
| 139 | + (0x3051, 0x42), |
| 140 | + (0x3052, 0x50), |
| 141 | + (0x3053, 0x00), |
| 142 | + (0x3054, 0x03), # tuning sf sig clamping as lowest |
| 143 | + (0x3055, 0xF7), # tuning dsun |
| 144 | + (0x3056, 0xF8), # increase adc nonoverlap clk |
| 145 | + (0x3057, 0x29), # increase adc pwr for missing code |
| 146 | + (0x3058, 0x1F), # turn on dsun |
| 147 | + (0x3059, 0x1E), |
| 148 | + (0x3064, 0x00), |
| 149 | + (0x3065, 0x04), # pad pull 0 |
| 150 | + (ANA_Register_17, 0x00), # Disable internal oscillator |
| 151 | + |
| 152 | + (0x1012, 0x00), # Sync. shift disable |
| 153 | + |
| 154 | + (AE_CTRL, 0x01), #Automatic Exposure |
| 155 | + (AE_TARGET_MEAN, 0x80), #AE target mean [Def: 0x3C] |
| 156 | + (AE_MIN_MEAN, 0x0A), #AE min target mean [Def: 0x0A] |
| 157 | + (CONVERGE_IN_TH, 0x03), #Converge in threshold [Def: 0x03] |
| 158 | + (CONVERGE_OUT_TH, 0x05), #Converge out threshold [Def: 0x05] |
| 159 | + (MAX_INTG_H, (HIMAX_FRAME_LENGTH_QVGA - 2) >> 8), #Maximum INTG High Byte [Def: 0x01] |
| 160 | + (MAX_INTG_L, (HIMAX_FRAME_LENGTH_QVGA - 2) & 0xFF), #Maximum INTG Low Byte [Def: 0x54] |
| 161 | + (MAX_AGAIN_FULL, 0x04), #Maximum Analog gain in full frame mode [Def: 0x03] |
| 162 | + (MAX_AGAIN_BIN2, 0x04), #Maximum Analog gain in bin2 mode [Def: 0x04] |
| 163 | + (MAX_DGAIN, 0xC0), |
| 164 | + |
| 165 | + (INTEGRATION_H, 0x01), #Integration H [Def: 0x01] |
| 166 | + (INTEGRATION_L, 0x08), #Integration L [Def: 0x08] |
| 167 | + (ANALOG_GAIN, 0x00), #Analog Global Gain [Def: 0x00] |
| 168 | + (DAMPING_FACTOR, 0x20), #Damping Factor [Def: 0x20] |
| 169 | + (DIGITAL_GAIN_H, 0x01), #Digital Gain High [Def: 0x01] |
| 170 | + (DIGITAL_GAIN_L, 0x00), #Digital Gain Low [Def: 0x00] |
| 171 | + |
| 172 | + (MD_CTRL, 0x00), |
| 173 | + (FRAME_LEN_LINES_H, HIMAX_FRAME_LENGTH_QVGA >> 8), |
| 174 | + (FRAME_LEN_LINES_L, HIMAX_FRAME_LENGTH_QVGA & 0xFF), |
| 175 | + (LINE_LEN_PCK_H, HIMAX_LINE_LEN_PCK_QVGA >> 8), |
| 176 | + (LINE_LEN_PCK_L, HIMAX_LINE_LEN_PCK_QVGA & 0xFF), |
| 177 | + (QVGA_WIN_EN, 0x01), # Enable QVGA window readout |
| 178 | + (0x3059, 0x22), # 1-bit mode |
| 179 | + (OSC_CLK_DIV, 0x14), |
| 180 | + (IMG_ORIENTATION, 0x00), # change the orientation |
| 181 | + (0x0104, 0x01), |
| 182 | + (MODE_SELECT, 0x01), # Streaming mode |
| 183 | + ) |
| 184 | + |
| 185 | + def __init__( |
| 186 | + self, |
| 187 | + i2c, |
| 188 | + pin_d0, |
| 189 | + pin_vsync, |
| 190 | + pin_hsync, |
| 191 | + pin_pclk, |
| 192 | + sm_id = 0, |
| 193 | + i2c_address = 0x24, |
| 194 | + ): |
| 195 | + self.i2c = i2c |
| 196 | + self.pin_d0 = pin_d0 |
| 197 | + self.pin_vsync = pin_vsync |
| 198 | + self.pin_hsync = pin_hsync |
| 199 | + self.pin_pclk = pin_pclk |
| 200 | + self.sm_id = sm_id |
| 201 | + self.i2c_address = i2c_address |
| 202 | + self.buffer = np.zeros((244, 324), dtype=np.uint8) |
| 203 | + # self.buffer = bytearray(244 * 324) |
| 204 | + |
| 205 | + Pin(pin_d0, Pin.IN) |
| 206 | + Pin(pin_vsync, Pin.IN) |
| 207 | + Pin(pin_hsync, Pin.IN) |
| 208 | + Pin(pin_pclk, Pin.IN) |
| 209 | + |
| 210 | + self.soft_reset() |
| 211 | + self.send_init() |
| 212 | + self.start_pio_dma() |
| 213 | + |
| 214 | + def is_connected(self): |
| 215 | + try: |
| 216 | + # Try to read the chip ID |
| 217 | + # If it throws an I/O error - the device isn't connected |
| 218 | + id = self.getChipID() |
| 219 | + |
| 220 | + # Confirm the chip ID is correct |
| 221 | + if id == 0x01B0: |
| 222 | + return True |
| 223 | + else: |
| 224 | + return False |
| 225 | + except: |
| 226 | + return False |
| 227 | + |
| 228 | + def getChipID(self): |
| 229 | + """ |
| 230 | + Reads the chip ID from the HM01B0 sensor. |
| 231 | + Returns: |
| 232 | + int: The chip ID as a 16-bit integer. |
| 233 | + """ |
| 234 | + data = self.readRegister(self.MODEL_ID_H, 2) |
| 235 | + return (data[0] << 8) | data[1] |
| 236 | + |
| 237 | + def soft_reset(self): |
| 238 | + """ |
| 239 | + Performs a software reset of the HM01B0 sensor. |
| 240 | + This resets the sensor to its default state. |
| 241 | + """ |
| 242 | + self.writeRegister(self.SW_RESET, self.HIMAX_RESET) |
| 243 | + |
| 244 | + def send_init(self): |
| 245 | + """ |
| 246 | + Initializes the HM01B0 sensor with default settings. |
| 247 | + This includes setting up exposure, gain, and frame timing. |
| 248 | + """ |
| 249 | + for reg, value in self.INIT_COMMANDS: |
| 250 | + self.writeRegister(reg, value) |
| 251 | + sleep_us(1000) |
| 252 | + |
| 253 | + # Ensure the sensor is in streaming mode |
| 254 | + # self.writeRegister(self.MODE_SELECT, self.HIMAX_MODE_STREAMING) |
| 255 | + |
| 256 | + def readRegister(self, reg, nbytes=1): |
| 257 | + self.i2c.writeto(self.i2c_address, bytes([reg >> 8, reg & 0xFF])) |
| 258 | + return self.i2c.readfrom(self.i2c_address, nbytes) |
| 259 | + |
| 260 | + def writeRegister(self, reg, data): |
| 261 | + if isinstance(data, int): |
| 262 | + data = bytes([data]) |
| 263 | + elif isinstance(data, (list, tuple)): |
| 264 | + data = bytes(data) |
| 265 | + self.i2c.writeto(self.i2c_address, bytes([reg >> 8, reg & 0xFF]) + data) |
| 266 | + |
| 267 | + def start_pio_dma(self): |
| 268 | + program = self._pio_read_dvp |
| 269 | + program[0][0] |= self.pin_hsync & 0x1F |
| 270 | + program[0][1] |= self.pin_pclk & 0x1F |
| 271 | + program[0][3] |= self.pin_pclk & 0x1F |
| 272 | + self.sm = rp2.StateMachine( |
| 273 | + self.sm_id, |
| 274 | + program, |
| 275 | + in_base = self.pin_d0 |
| 276 | + ) |
| 277 | + self.sm.active(1) |
| 278 | + |
| 279 | + self.dma = rp2.DMA() |
| 280 | + req_num = ((self.sm_id // 4) << 3) + (self.sm_id % 4) + 4 |
| 281 | + dma_ctrl = self.dma.pack_ctrl( |
| 282 | + size = 0, # 0 = 8-bit, 1 = 16-bit, 2 = 32-bit |
| 283 | + inc_read = False, |
| 284 | + treq_sel = req_num |
| 285 | + # irq_quiet = False |
| 286 | + ) |
| 287 | + self.dma.config( |
| 288 | + read = self.sm, |
| 289 | + write = self.buffer, |
| 290 | + count = 244 * 324, |
| 291 | + ctrl = dma_ctrl |
| 292 | + ) |
| 293 | + |
| 294 | + Pin(self.pin_vsync).irq( |
| 295 | + trigger = Pin.IRQ_FALLING, |
| 296 | + handler = lambda pin: self._vsync_handler() |
| 297 | + ) |
| 298 | + |
| 299 | + def _vsync_handler(self): |
| 300 | + self.sm.restart() |
| 301 | + self.dma.write = self.buffer |
| 302 | + self.dma.active(True) |
| 303 | + # print("new frame:", time.ticks_ms()) |
| 304 | + |
| 305 | + @rp2.asm_pio( |
| 306 | + in_shiftdir = rp2.PIO.SHIFT_LEFT, |
| 307 | + push_thresh = 8, |
| 308 | + autopush = True |
| 309 | + ) |
| 310 | + def _pio_read_dvp(): |
| 311 | + wait(1, gpio, 0) # Mask in HSYNC pin |
| 312 | + wait(1, gpio, 0) # Mask in PCLK pin |
| 313 | + in_(pins, 1) # Mask in number of pins |
| 314 | + wait(0, gpio, 0) # Mask in PCLK pin |
0 commit comments