Kiibohd Controller
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
このリポジトリはアーカイブされています。 ファイルの閲覧とクローンは可能ですが、プッシュや、課題・プルリクエストのオープンはできません。

scan_loop.c 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. /* Copyright (C) 2014 by Jacob Alexander
  2. *
  3. * Permission is hereby granted, free of charge, to any person obtaining a copy
  4. * of this software and associated documentation files (the "Software"), to deal
  5. * in the Software without restriction, including without limitation the rights
  6. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. * copies of the Software, and to permit persons to whom the Software is
  8. * furnished to do so, subject to the following conditions:
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. * THE SOFTWARE.
  20. */
  21. // ----- Includes -----
  22. // Compiler Includes
  23. #include <Lib/ScanLib.h>
  24. // Project Includes
  25. #include <cli.h>
  26. #include <led.h>
  27. #include <print.h>
  28. #include <matrix_scan.h>
  29. // Local Includes
  30. #include "scan_loop.h"
  31. #include "macro.h"
  32. typedef struct I2C_Buffer {
  33. volatile uint16_t head;
  34. volatile uint16_t tail;
  35. volatile uint8_t sequencePos;
  36. volatile uint16_t size;
  37. volatile uint8_t *buffer;
  38. } I2C_Buffer;
  39. // ----- Function Declarations -----
  40. // CLI Functions
  41. void cliFunc_echo( char* args );
  42. void cliFunc_i2cRecv( char* args );
  43. void cliFunc_i2cSend( char* args );
  44. void cliFunc_ledZero( char* args );
  45. uint8_t I2C_TxBufferPop();
  46. void I2C_BufferPush( uint8_t byte, I2C_Buffer *buffer );
  47. uint16_t I2C_BufferLen( I2C_Buffer *buffer );
  48. // ----- Variables -----
  49. // Scan Module command dictionary
  50. CLIDict_Entry( echo, "Example command, echos the arguments." );
  51. CLIDict_Entry( i2cRecv, "Send I2C sequence of bytes and expect a reply of 1 byte." );
  52. CLIDict_Entry( i2cSend, "Send I2C sequence of bytes." );
  53. CLIDict_Entry( ledZero, "Zero out LED register pages (non-configuration)." );
  54. CLIDict_Def( scanCLIDict, "Scan Module Commands" ) = {
  55. CLIDict_Item( echo ),
  56. CLIDict_Item( i2cRecv ),
  57. CLIDict_Item( i2cSend ),
  58. CLIDict_Item( ledZero ),
  59. { 0, 0, 0 } // Null entry for dictionary end
  60. };
  61. // Number of scans since the last USB send
  62. uint16_t Scan_scanCount = 0;
  63. // Before sending the sequence, I2C_TxBuffer_CurLen is assigned and as each byte is sent, it is decremented
  64. // Once I2C_TxBuffer_CurLen reaches zero, a STOP on the I2C bus is sent
  65. #define I2C_TxBufferLength 300
  66. #define I2C_RxBufferLength 8
  67. volatile uint8_t I2C_TxBufferPtr[ I2C_TxBufferLength ];
  68. volatile uint8_t I2C_RxBufferPtr[ I2C_TxBufferLength ];
  69. volatile I2C_Buffer I2C_TxBuffer = { 0, 0, 0, I2C_TxBufferLength, I2C_TxBufferPtr };
  70. volatile I2C_Buffer I2C_RxBuffer = { 0, 0, 0, I2C_RxBufferLength, I2C_RxBufferPtr };
  71. void I2C_setup()
  72. {
  73. // Enable I2C internal clock
  74. SIM_SCGC4 |= SIM_SCGC4_I2C0; // Bus 0
  75. // External pull-up resistor
  76. PORTB_PCR0 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
  77. PORTB_PCR1 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
  78. // SCL Frequency Divider
  79. // 400kHz -> 120 (0x85) @ 48 MHz F_BUS
  80. I2C0_F = 0x85;
  81. I2C0_FLT = 4;
  82. I2C0_C1 = I2C_C1_IICEN;
  83. I2C0_C2 = I2C_C2_HDRS; // High drive select
  84. // Enable I2C Interrupt
  85. NVIC_ENABLE_IRQ( IRQ_I2C0 );
  86. }
  87. // ----- Interrupt Functions -----
  88. void i2c0_isr()
  89. {
  90. cli(); // Disable Interrupts
  91. uint8_t status = I2C0_S; // Read I2C Bus status
  92. // Master Mode Transmit
  93. if ( I2C0_C1 & I2C_C1_TX )
  94. {
  95. // Check current use of the I2C bus
  96. // Currently sending data
  97. if ( I2C_TxBuffer.sequencePos > 0 )
  98. {
  99. // Make sure slave sent an ACK
  100. if ( status & I2C_S_RXAK )
  101. {
  102. // NACK Detected, disable interrupt
  103. erro_print("I2C NAK detected...");
  104. I2C0_C1 = I2C_C1_IICEN;
  105. // Abort Tx Buffer
  106. I2C_TxBuffer.head = 0;
  107. I2C_TxBuffer.tail = 0;
  108. I2C_TxBuffer.sequencePos = 0;
  109. }
  110. else
  111. {
  112. // Transmit byte
  113. I2C0_D = I2C_TxBufferPop();
  114. }
  115. }
  116. // Receiving data
  117. else if ( I2C_RxBuffer.sequencePos > 0 )
  118. {
  119. // Master Receive, addr sent
  120. if ( status & I2C_S_ARBL )
  121. {
  122. // Arbitration Lost
  123. erro_print("Arbitration lost...");
  124. // TODO Abort Rx
  125. I2C0_C1 = I2C_C1_IICEN;
  126. I2C0_S = I2C_S_ARBL | I2C_S_IICIF; // Clear ARBL flag and interrupt
  127. }
  128. if ( status & I2C_S_RXAK )
  129. {
  130. // Slave Address NACK Detected, disable interrupt
  131. erro_print("Slave Address I2C NAK detected...");
  132. // TODO Abort Rx
  133. I2C0_C1 = I2C_C1_IICEN;
  134. }
  135. else
  136. {
  137. I2C0_C1 = I2C_RxBuffer.sequencePos == 1
  138. ? I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK // Single byte read
  139. : I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST; // Multi-byte read
  140. }
  141. }
  142. else
  143. {
  144. dbug_msg("STOP - ");
  145. printHex( I2C_BufferLen( (I2C_Buffer*)&I2C_TxBuffer ) );
  146. print(NL);
  147. // If there is another sequence, start sending
  148. if ( I2C_BufferLen( (I2C_Buffer*)&I2C_TxBuffer ) < I2C_TxBuffer.size )
  149. {
  150. // Check to see if we already have control of the bus
  151. if ( I2C0_C1 & I2C_C1_MST )
  152. {
  153. // Already the master (ah yeah), send a repeated start
  154. I2C0_C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX;
  155. }
  156. // Otherwise, seize control
  157. else
  158. {
  159. // Wait...till the master dies
  160. while ( I2C0_S & I2C_S_BUSY );
  161. // Now we're the master (ah yisss), get ready to send stuffs
  162. I2C0_C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
  163. }
  164. // Enable I2C interrupt
  165. I2C0_C1 = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX;
  166. // Transmit byte
  167. I2C0_D = I2C_TxBufferPop();
  168. }
  169. // Issue STOP
  170. else
  171. {
  172. delayMicroseconds( 1 ); // Should be enough time before issuing STOP
  173. I2C0_C1 = I2C_C1_IICEN; // Send STOP
  174. }
  175. }
  176. }
  177. // Master Mode Receive
  178. else
  179. {
  180. // XXX Do we need to handle 2nd last byte?
  181. //I2C0_C1 = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // No STOP, Rx, NAK on recv
  182. // Last byte
  183. if ( I2C_TxBuffer.sequencePos <= 1 )
  184. {
  185. // Change to Tx mode
  186. I2C0_C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
  187. // Grab last byte
  188. I2C_BufferPush( I2C0_D, (I2C_Buffer*)&I2C_RxBuffer );
  189. delayMicroseconds( 1 ); // Should be enough time before issuing the stop
  190. I2C0_C1 = I2C_C1_IICEN; // Send STOP
  191. }
  192. else
  193. {
  194. // Retrieve data
  195. I2C_BufferPush( I2C0_D, (I2C_Buffer*)&I2C_RxBuffer );
  196. }
  197. }
  198. I2C0_S = I2C_S_IICIF; // Clear interrupt
  199. sei(); // Re-enable Interrupts
  200. }
  201. // ----- Functions -----
  202. // Setup
  203. inline void LED_setup()
  204. {
  205. I2C_setup();
  206. }
  207. inline uint8_t I2C_BufferCopy( uint8_t *data, uint8_t sendLen, uint8_t recvLen, I2C_Buffer *buffer )
  208. {
  209. uint8_t reTurn = 0;
  210. // If sendLen is greater than buffer fail right away
  211. if ( sendLen > buffer->size )
  212. return 0;
  213. // Calculate new tail to determine if buffer has enough space
  214. // The first element specifies the expected number of bytes from the slave (+1)
  215. // The second element in the new buffer is the length of the buffer sequence (+1)
  216. uint16_t newTail = buffer->tail + sendLen + 2;
  217. if ( newTail >= buffer->size )
  218. newTail -= buffer->size;
  219. if ( I2C_BufferLen( buffer ) < sendLen + 2 )
  220. return 0;
  221. /*
  222. print("|");
  223. printHex( sendLen + 2 );
  224. print("|");
  225. printHex( *tail );
  226. print("@");
  227. printHex( newTail );
  228. print("@");
  229. */
  230. // If buffer is clean, return 1, otherwise 2
  231. reTurn = buffer->head == buffer->tail ? 1 : 2;
  232. // Add to buffer, already know there is enough room (simplifies adding logic)
  233. uint8_t bufferHeaderPos = 0;
  234. for ( uint16_t c = 0; c < sendLen; c++ )
  235. {
  236. // Add data to buffer
  237. switch ( bufferHeaderPos )
  238. {
  239. case 0:
  240. buffer->buffer[ buffer->tail ] = recvLen;
  241. bufferHeaderPos++;
  242. c--;
  243. break;
  244. case 1:
  245. buffer->buffer[ buffer->tail ] = sendLen;
  246. bufferHeaderPos++;
  247. c--;
  248. break;
  249. default:
  250. buffer->buffer[ buffer->tail ] = data[ c ];
  251. break;
  252. }
  253. // Check for wrap-around case
  254. if ( buffer->tail + 1 >= buffer->size )
  255. {
  256. buffer->tail = 0;
  257. }
  258. // Normal case
  259. else
  260. {
  261. buffer->tail++;
  262. }
  263. }
  264. return reTurn;
  265. }
  266. inline uint16_t I2C_BufferLen( I2C_Buffer *buffer )
  267. {
  268. // Tail >= Head
  269. if ( buffer->tail >= buffer->head )
  270. return buffer->head + buffer->size - buffer->tail;
  271. // Head > Tail
  272. return buffer->head - buffer->tail;
  273. }
  274. void I2C_BufferPush( uint8_t byte, I2C_Buffer *buffer )
  275. {
  276. // Make sure buffer isn't full
  277. if ( buffer->tail + 1 == buffer->head || ( buffer->head > buffer->tail && buffer->tail + 1 - buffer->size == buffer->head ) )
  278. {
  279. warn_msg("I2C_BufferPush failed, buffer full: ");
  280. printHex( byte );
  281. print( NL );
  282. return;
  283. }
  284. // Check for wrap-around case
  285. if ( buffer->tail + 1 >= buffer->size )
  286. {
  287. buffer->tail = 0;
  288. }
  289. // Normal case
  290. else
  291. {
  292. buffer->tail++;
  293. }
  294. // Add byte to buffer
  295. buffer->buffer[ buffer->tail ] = byte;
  296. }
  297. uint8_t I2C_TxBufferPop()
  298. {
  299. // Return 0xFF if no buffer left (do not rely on this)
  300. if ( I2C_BufferLen( (I2C_Buffer*)&I2C_TxBuffer ) >= I2C_TxBuffer.size )
  301. {
  302. erro_msg("No buffer to pop an entry from... ");
  303. printHex( I2C_TxBuffer.head );
  304. print(" ");
  305. printHex( I2C_TxBuffer.tail );
  306. print(" ");
  307. printHex( I2C_TxBuffer.sequencePos );
  308. print(NL);
  309. return 0xFF;
  310. }
  311. // If there is currently no sequence being sent, the first entry in the RingBuffer is the length
  312. if ( I2C_TxBuffer.sequencePos == 0 )
  313. {
  314. I2C_TxBuffer.sequencePos = 0xFF; // So this doesn't become an infinite loop
  315. I2C_RxBuffer.sequencePos = I2C_TxBufferPop();
  316. I2C_TxBuffer.sequencePos = I2C_TxBufferPop();
  317. }
  318. uint8_t data = I2C_TxBuffer.buffer[ I2C_TxBuffer.head ];
  319. // Prune head
  320. I2C_TxBuffer.head++;
  321. // Wrap-around case
  322. if ( I2C_TxBuffer.head >= I2C_TxBuffer.size )
  323. I2C_TxBuffer.head = 0;
  324. // Decrement buffer sequence (until next stop will be sent)
  325. I2C_TxBuffer.sequencePos--;
  326. dbug_msg("Popping: ");
  327. printHex( data );
  328. print(" ");
  329. printHex( I2C_TxBuffer.head );
  330. print(" ");
  331. printHex( I2C_TxBuffer.tail );
  332. print(" ");
  333. printHex( I2C_TxBuffer.sequencePos );
  334. print(NL);
  335. return data;
  336. }
  337. uint8_t I2C_Send( uint8_t *data, uint8_t sendLen, uint8_t recvLen )
  338. {
  339. // Check head and tail pointers
  340. // If full, return 0
  341. // If empty, start up I2C Master Tx
  342. // If buffer is non-empty and non-full, just append to the buffer
  343. switch ( I2C_BufferCopy( data, sendLen, recvLen, (I2C_Buffer*)&I2C_TxBuffer ) )
  344. {
  345. // Not enough buffer space...
  346. case 0:
  347. /*
  348. erro_msg("Not enough Tx buffer space... ");
  349. printHex( I2C_TxBuffer.head );
  350. print(":");
  351. printHex( I2C_TxBuffer.tail );
  352. print("+");
  353. printHex( sendLen );
  354. print("|");
  355. printHex( I2C_TxBuffer.size );
  356. print( NL );
  357. */
  358. return 0;
  359. // Empty buffer, initialize I2C
  360. case 1:
  361. // Clear status flags
  362. I2C0_S = I2C_S_IICIF | I2C_S_ARBL;
  363. // Check to see if we already have control of the bus
  364. if ( I2C0_C1 & I2C_C1_MST )
  365. {
  366. // Already the master (ah yeah), send a repeated start
  367. I2C0_C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX;
  368. }
  369. // Otherwise, seize control
  370. else
  371. {
  372. // Wait...till the master dies
  373. while ( I2C0_S & I2C_S_BUSY );
  374. // Now we're the master (ah yisss), get ready to send stuffs
  375. I2C0_C1 = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
  376. }
  377. // Enable I2C interrupt
  378. I2C0_C1 = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX;
  379. // Depending on what type of transfer, the first byte is configured for R or W
  380. I2C0_D = I2C_TxBufferPop();
  381. return 1;
  382. }
  383. // Dirty buffer, I2C already initialized
  384. return 2;
  385. }
  386. void LED_zeroPages( uint8_t startPage, uint8_t numPages, uint8_t pageLen )
  387. {
  388. // Page Setup
  389. uint8_t pageSetup[] = { 0xE8, 0xFD, 0x00 };
  390. // Max length of a page + chip id + reg start
  391. uint8_t fullPage[ 0xB3 + 2 ] = { 0 };
  392. fullPage[0] = 0xE8; // Set chip id, starting reg is already 0x00
  393. // Iterate through given pages, zero'ing out the given register regions
  394. for ( uint8_t page = startPage; page < startPage + numPages; page++ )
  395. {
  396. // Set page
  397. pageSetup[2] = page;
  398. // Setup page
  399. while ( I2C_Send( pageSetup, sizeof( pageSetup ), 0 ) == 0 )
  400. delay(1);
  401. // Zero out page
  402. while ( I2C_Send( fullPage, pageLen + 2, 0 ) == 0 )
  403. delay(1);
  404. }
  405. }
  406. // LED State processing loop
  407. inline uint8_t LED_loop()
  408. {
  409. // I2C Busy
  410. // S & I2C_S_BUSY
  411. //I2C_S_BUSY
  412. }
  413. // Setup
  414. inline void Scan_setup()
  415. {
  416. // Register Scan CLI dictionary
  417. CLI_registerDictionary( scanCLIDict, scanCLIDictName );
  418. // Setup GPIO pins for matrix scanning
  419. //Matrix_setup();
  420. // Reset scan count
  421. Scan_scanCount = 0;
  422. // Setup LED Drivers
  423. LED_setup();
  424. }
  425. // Main Detection Loop
  426. inline uint8_t Scan_loop()
  427. {
  428. //Matrix_scan( Scan_scanCount++ );
  429. //LED_scan();
  430. return 0;
  431. }
  432. // Signal from Macro Module that all keys have been processed (that it knows about)
  433. inline void Scan_finishedWithMacro( uint8_t sentKeys )
  434. {
  435. }
  436. // Signal from Output Module that all keys have been processed (that it knows about)
  437. inline void Scan_finishedWithOutput( uint8_t sentKeys )
  438. {
  439. // Reset scan loop indicator (resets each key debounce state)
  440. // TODO should this occur after USB send or Macro processing?
  441. Scan_scanCount = 0;
  442. }
  443. // ----- CLI Command Functions -----
  444. // XXX Just an example command showing how to parse arguments (more complex than generally needed)
  445. void cliFunc_echo( char* args )
  446. {
  447. char* curArgs;
  448. char* arg1Ptr;
  449. char* arg2Ptr = args;
  450. // Parse args until a \0 is found
  451. while ( 1 )
  452. {
  453. print( NL ); // No \r\n by default after the command is entered
  454. curArgs = arg2Ptr; // Use the previous 2nd arg pointer to separate the next arg from the list
  455. CLI_argumentIsolation( curArgs, &arg1Ptr, &arg2Ptr );
  456. // Stop processing args if no more are found
  457. if ( *arg1Ptr == '\0' )
  458. break;
  459. // Print out the arg
  460. dPrint( arg1Ptr );
  461. }
  462. }
  463. void cliFunc_i2cSend( char* args )
  464. {
  465. char* curArgs;
  466. char* arg1Ptr;
  467. char* arg2Ptr = args;
  468. // Buffer used after interpretting the args, will be sent to I2C functions
  469. // NOTE: Limited to 8 bytes currently (can be increased if necessary
  470. #define i2cSend_BuffLenMax 8
  471. uint8_t buffer[ i2cSend_BuffLenMax ];
  472. uint8_t bufferLen = 0;
  473. // No \r\n by default after the command is entered
  474. print( NL );
  475. info_msg("Sending: ");
  476. // Parse args until a \0 is found
  477. while ( bufferLen < i2cSend_BuffLenMax )
  478. {
  479. curArgs = arg2Ptr; // Use the previous 2nd arg pointer to separate the next arg from the list
  480. CLI_argumentIsolation( curArgs, &arg1Ptr, &arg2Ptr );
  481. // Stop processing args if no more are found
  482. if ( *arg1Ptr == '\0' )
  483. break;
  484. // Interpret the argument
  485. buffer[ bufferLen++ ] = (uint8_t)numToInt( arg1Ptr );
  486. // Print out the arg
  487. dPrint( arg1Ptr );
  488. print(" ");
  489. }
  490. print( NL );
  491. I2C_Send( buffer, bufferLen, 0 );
  492. }
  493. void cliFunc_i2cRecv( char* args )
  494. {
  495. char* curArgs;
  496. char* arg1Ptr;
  497. char* arg2Ptr = args;
  498. // Buffer used after interpretting the args, will be sent to I2C functions
  499. // NOTE: Limited to 8 bytes currently (can be increased if necessary
  500. #define i2cSend_BuffLenMax 8
  501. uint8_t buffer[ i2cSend_BuffLenMax ];
  502. uint8_t bufferLen = 0;
  503. // No \r\n by default after the command is entered
  504. print( NL );
  505. info_msg("Sending: ");
  506. // Parse args until a \0 is found
  507. while ( bufferLen < i2cSend_BuffLenMax )
  508. {
  509. curArgs = arg2Ptr; // Use the previous 2nd arg pointer to separate the next arg from the list
  510. CLI_argumentIsolation( curArgs, &arg1Ptr, &arg2Ptr );
  511. // Stop processing args if no more are found
  512. if ( *arg1Ptr == '\0' )
  513. break;
  514. // Interpret the argument
  515. buffer[ bufferLen++ ] = (uint8_t)numToInt( arg1Ptr );
  516. // Print out the arg
  517. dPrint( arg1Ptr );
  518. print(" ");
  519. }
  520. print( NL );
  521. I2C_Send( buffer, bufferLen, 1 ); // Only 1 byte is ever read at a time with the ISSI chip
  522. }
  523. void cliFunc_ledZero( char* args )
  524. {
  525. print( NL ); // No \r\n by default after the command is entered
  526. LED_zeroPages( 0x00, 8, 0xB3 );
  527. }