tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/ws2812/ws2812_xtensa.go (about) 1 //go:build xtensa 2 3 package ws2812 4 5 import ( 6 "device" 7 "machine" 8 "runtime/interrupt" 9 "unsafe" 10 ) 11 12 func (d Device) WriteByte(c byte) error { 13 portSet, maskSet := d.Pin.PortMaskSet() 14 portClear, maskClear := d.Pin.PortMaskClear() 15 mask := interrupt.Disable() 16 17 switch machine.CPUFrequency() { 18 case 160e6: // 160MHz 19 // See: 20 // https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ 21 // Because I do not know the exact instruction timings, I'm going to 22 // assume that every instruction executes in one cycle. Branches and 23 // load/stores will probably be slower than that, but as long as all 24 // timings are only increased a little bit this should not be a problem 25 // (see above post). 26 // T0H: 40 cycles or 333.3ns 27 // T0L: 131 cycles or 1091.7ns 28 // +: 171 cycles or 1425.0ns 29 // T1H: 95 cycles or 791.7ns 30 // T1L: 75 cycles or 625.0ns 31 // +: 170 cycles or 1416.7ns 32 // Some documentation: 33 // http://cholla.mmto.org/esp8266/xtensa.html 34 // https://0x04.net/~mwk/doc/xtensa.pdf 35 device.AsmFull(` 36 1: // send_bit 37 s32i {maskSet}, {portSet}, 0 // [1] T0H and T1H start here 38 nop // [37] 39 nop 40 nop 41 nop 42 nop 43 nop 44 nop 45 nop 46 nop 47 nop 48 nop 49 nop 50 nop 51 nop 52 nop 53 nop 54 nop 55 nop 56 nop 57 nop 58 nop 59 nop 60 nop 61 nop 62 nop 63 nop 64 nop 65 nop 66 nop 67 nop 68 nop 69 nop 70 nop 71 nop 72 nop 73 nop 74 nop 75 slli {value}, {value}, 1 // [1] shift {value} to the left by 1 76 bbsi {value}, 8, 2f // [1] branch to skip_store if bit 8 is set 77 s32i {maskClear}, {portClear}, 0 // [1] T0H -> T0L transition 78 2: // skip_store 79 nop // [55] 80 nop 81 nop 82 nop 83 nop 84 nop 85 nop 86 nop 87 nop 88 nop 89 nop 90 nop 91 nop 92 nop 93 nop 94 nop 95 nop 96 nop 97 nop 98 nop 99 nop 100 nop 101 nop 102 nop 103 nop 104 nop 105 nop 106 nop 107 nop 108 nop 109 nop 110 nop 111 nop 112 nop 113 nop 114 nop 115 nop 116 nop 117 nop 118 nop 119 nop 120 nop 121 nop 122 nop 123 nop 124 nop 125 nop 126 nop 127 nop 128 nop 129 nop 130 nop 131 nop 132 nop 133 nop 134 s32i {maskClear}, {portClear}, 0 // [1] T1H -> T1L transition 135 nop // [72] 136 nop 137 nop 138 nop 139 nop 140 nop 141 nop 142 nop 143 nop 144 nop 145 nop 146 nop 147 nop 148 nop 149 nop 150 nop 151 nop 152 nop 153 nop 154 nop 155 nop 156 nop 157 nop 158 nop 159 nop 160 nop 161 nop 162 nop 163 nop 164 nop 165 nop 166 nop 167 nop 168 nop 169 nop 170 nop 171 nop 172 nop 173 nop 174 nop 175 nop 176 nop 177 nop 178 nop 179 nop 180 nop 181 nop 182 nop 183 nop 184 nop 185 nop 186 nop 187 nop 188 nop 189 nop 190 nop 191 nop 192 nop 193 nop 194 nop 195 nop 196 nop 197 nop 198 nop 199 nop 200 nop 201 nop 202 nop 203 nop 204 nop 205 nop 206 nop 207 addi {i}, {i}, -1 // [1] 208 bnez {i}, 1b // [1] send_bit, T1H and T1L end here 209 210 // Restore original values after modifying them in the inline 211 // assembly. Not doing that would result in undefined behavior as 212 // the compiler doesn't know we're modifying these values. 213 movi.n {i}, 8 214 slli {value}, {value}, 8 215 `, map[string]interface{}{ 216 // Note: casting pointers to uintptr here because of what might be 217 // an Xtensa backend bug with inline assembly. 218 "value": uint32(c), 219 "i": 8, 220 "maskSet": maskSet, 221 "portSet": uintptr(unsafe.Pointer(portSet)), 222 "maskClear": maskClear, 223 "portClear": uintptr(unsafe.Pointer(portClear)), 224 }) 225 interrupt.Restore(mask) 226 return nil 227 case 80e6: // 80MHz 228 // See docs for 160MHz. 229 // T0H: 21 cycles or 262.5ns 230 // T0L: 67 cycles or 837.5ns 231 // +: 88 cycles or 1100.0ns 232 // T1H: 47 cycles or 587.5ns 233 // T1L: 39 cycles or 487.5ns 234 // +: 86 cycles or 1075.0ns 235 device.AsmFull(` 236 1: // send_bit 237 s32i {maskSet}, {portSet}, 0 // [1] T0H and T1H start here 238 nop // [18] 239 nop 240 nop 241 nop 242 nop 243 nop 244 nop 245 nop 246 nop 247 nop 248 nop 249 nop 250 nop 251 nop 252 nop 253 nop 254 nop 255 nop 256 slli {value}, {value}, 1 // [1] shift {value} to the left by 1 257 bbsi {value}, 8, 2f // [1] branch to skip_store if bit 8 is set 258 s32i {maskClear}, {portClear}, 0 // [1] T0H -> T0L transition 259 2: // skip_store 260 nop // [27] 261 nop 262 nop 263 nop 264 nop 265 nop 266 nop 267 nop 268 nop 269 nop 270 nop 271 nop 272 nop 273 nop 274 nop 275 nop 276 nop 277 nop 278 nop 279 nop 280 nop 281 nop 282 nop 283 nop 284 nop 285 nop 286 nop 287 s32i {maskClear}, {portClear}, 0 // [1] T1H -> T1L transition 288 nop // [36] 289 nop 290 nop 291 nop 292 nop 293 nop 294 nop 295 nop 296 nop 297 nop 298 nop 299 nop 300 nop 301 nop 302 nop 303 nop 304 nop 305 nop 306 nop 307 nop 308 nop 309 nop 310 nop 311 nop 312 nop 313 nop 314 nop 315 nop 316 nop 317 nop 318 nop 319 nop 320 nop 321 nop 322 nop 323 nop 324 addi {i}, {i}, -1 // [1] 325 bnez {i}, 1b // [1] send_bit, T1H and T1L end here 326 327 // Restore original values after modifying them in the inline 328 // assembly. Not doing that would result in undefined behavior as 329 // the compiler doesn't know we're modifying these values. 330 movi.n {i}, 8 331 slli {value}, {value}, 8 332 `, map[string]interface{}{ 333 // Note: casting pointers to uintptr here because of what might be 334 // an Xtensa backend bug with inline assembly. 335 "value": uint32(c), 336 "i": 8, 337 "maskSet": maskSet, 338 "portSet": uintptr(unsafe.Pointer(portSet)), 339 "maskClear": maskClear, 340 "portClear": uintptr(unsafe.Pointer(portClear)), 341 }) 342 interrupt.Restore(mask) 343 return nil 344 default: 345 interrupt.Restore(mask) 346 return errUnknownClockSpeed 347 } 348 }