Some assembler required (PIC 16F59)

Discussion in 'Embedded Systems and Microcontrollers' started by takao21203, Feb 4, 2014.

  1. takao21203

    Thread Starter Distinguished Member

    Apr 28, 2012
    I am using 8x of these for my large LED matrix.
    Each controls 16x6 LEDs with 4bit for each LED.

    The only possibility to transfer data serially fast into the chip I saw was to code it in assembler.

    It is not so good to use on the sender side:
    There must be additional delay between each byte.

    Actually I even want to try hardware serial buffer with one smaller 14pin PIC for 2 16f59.

    The clocking scheme transfers a bit on rising and falling edge- saving time. And I have built in a timeout, this is reducing the speed a lot. Maybe I have to remove it somehow.

    There is not much time left, about 1mSec, to clock in 376 bits.
    Clock is 18 MHz.

    3uSec.bit / 200nSec MCU cycle (approx.)

    Here the source code:

    (for transfering 8bits)

    I could speed it up a little by transfering 16 bits each time.

    Maybe I also need to change some parts of the display control to assembler because it is too slow in C.
    I get 30 Hz for 96 LEDs / 16 brightness levels, but that is free running as fast as possible.

    Some speed is lost since I expand from 4bits storage to 8bit for each LED in a line.

    Code ( (Unknown Language)):
    2. unsigned char asm_status @0x10;
    3. unsigned char asm_x @0x11;
    4. unsigned char asm_y @0x12;
    6. void receive_bits(unsigned char x)
    7. {asm_x=x;
    9.  asm("banksel _asm_y");
    10.  asm("movf _asm_y,w");
    11.  asm("movwf FSR");
    12.  asm("movf _asm_x,w");
    13.  asm("addwf FSR,f");
    15.  asm("clrf TMR0");
    16.  asm("clrf INDF");
    17.  asm("reloop0:");
    18.  asm("btfss PORTB,1");asm("goto reloop0"); // wait for clock to go H
    19.  asm("btfss PORTB,0");asm("bsf INDF,0");// get data bit
    21.  asm("reloop1:");asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    22.  asm("btfsc PORTB,1");asm("goto reloop1"); // wait for clock to go L
    23.  asm("btfss PORTB,0");asm("bsf INDF,1");// get data bit
    24.  asm("reloop2:");asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    25.  asm("btfss PORTB,1");asm("goto reloop2"); // wait for clock to go H
    26.  asm("btfss PORTB,0");asm("bsf INDF,2");// get data bit
    27.  asm("reloop3:");asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    28.  asm("btfsc PORTB,1");asm("goto reloop3"); // wait for clock to go L
    29.  asm("btfss PORTB,0");asm("bsf INDF,3");// get data bit
    30.  asm("reloop4:");asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    31.  asm("btfss PORTB,1");asm("goto reloop4"); // wait for clock to go H
    32.  asm("btfss PORTB,0");asm("bsf INDF,4");// get data bit
    33.  asm("reloop5:");asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    34.  asm("btfsc PORTB,1");asm("goto reloop5"); // wait for clock to go L
    35.  asm("btfss PORTB,0");asm("bsf INDF,5");// get data bit
    36.  asm("reloop6:");asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    37.  asm("btfss PORTB,1");asm("goto reloop6"); // wait for clock to go H
    38.  asm("btfss PORTB,0");asm("bsf INDF,6");// get data bit
    39.  asm("reloop7:");asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    40.  asm("btfsc PORTB,1");asm("goto reloop7"); // wait for clock to go L
    42.  asm("btfss PORTB,0");asm("bsf INDF,7");// get data bit
    43.  asm("btfsc TMR0,7");asm("goto timeout");asm("clrf TMR0");
    45.  asm("bcf _asm_status,0");
    46.  asm("goto rdy");
    47.  asm("timeout:");
    48.  asm("bsf _asm_status,0");
    49.  asm("rdy:");
    50. }
  2. takao21203

    Thread Starter Distinguished Member

    Apr 28, 2012
    I don't really want to use assembler but it is the only way to speed up the LED matrix drive.

    Or I could overclock the PIC- it is apparently possible to run some PICs at double frequency.

    the project migrated a lot- at first, I used 74hc595 buffers (but errors creeped in because too many chips). Then I changed to 8x PIc 16f59, having lots of these PCBs around.

    All the faulty LEDs were replaced some day.

    The files were moved a few times to different computers, and MPLABX updated many times. Luckily I had them on my skydrive.

    A few days ago, I downloaded them to my netbook.

    Today I tried the new Microchip Configuration manager. For hardware serial port, i use a PIC 16f1824.

    I think when I run the serial lines from 8 LED matrix panels directly to the PIC32, it will be occupied too much.

    The timing is complicated- because the 16f59 has no external interrupt, the master must test for condition, and then signal it wants to transfer updates. Then the 16f59 must respond to that, indeed wait for such a response for a while or otherwise skip.

    Then it waits for bits to become clocked in.
    Maybe I use two bits at once or maybe it is not worth it.
    And I want to try to connect 2x 16f59 to one 16f1824 chip.

    The 16f1824 run from internal 8 MHz with 4x PLL.
  3. takao21203

    Thread Starter Distinguished Member

    Apr 28, 2012
    This is what I created tonight for the master controller:
    Code ( (Unknown Language)):
    2. void send8bits(unsigned char index)
    3. {
    4.  struct {
    5.   unsigned b1:1;
    6.   unsigned b2:1;
    7.   unsigned b3:1;
    8.   unsigned b4:1;
    9.   unsigned b5:1;
    10.   unsigned b6:1;
    11.   unsigned b7:1;
    12.   unsigned b8:1;  
    13.  }ser_data=spiReadBuffer[index];
    15. // CLK floating (input) + WPU=ON
    16. // pulled low by slave -> interrupt on master
    17. // master sets DAT to 1 if it wants to transfer data
    18. // slave waits for CLK to go high
    20. IO_RA0_serdat0_LAT=ser_data.b1;
    21. IO_RA1_serclk0_LAT=1;
    22. IO_RA0_serdat0_LAT=ser_data.b2;
    23. IO_RA1_serclk0_LAT=0;
    24. IO_RA0_serdat0_LAT=ser_data.b3;
    25. IO_RA1_serclk0_LAT=1;
    26. IO_RA0_serdat0_LAT=ser_data.b4;
    27. IO_RA1_serclk0_LAT=0;
    28. IO_RA0_serdat0_LAT=ser_data.b5;
    29. IO_RA1_serclk0_LAT=1;
    30. IO_RA0_serdat0_LAT=ser_data.b6;
    31. IO_RA1_serclk0_LAT=0;
    32. IO_RA0_serdat0_LAT=ser_data.b7;
    33. IO_RA1_serclk0_LAT=1;
    34. IO_RA0_serdat0_LAT=ser_data.b8;
    35. IO_RA1_serclk0_LAT=0;
    36. }
    There are 48 bytes to transfer.
    I never used bitfields so far...
  4. takao21203

    Thread Starter Distinguished Member

    Apr 28, 2012
    The resulting assembler code:

    Code ( (Unknown Language)):
    2. !void send8bits(unsigned char index)
    3. 0x35: MOVLB 0x0
    4. 0x36: MOVWF index
    5. !{
    6. ! union{
    7. !  unsigned char sd;
    8. !  struct {
    9. !   unsigned b1:1;
    10. !   unsigned b2:1;
    11. !   unsigned b3:1;
    12. !   unsigned b4:1;
    13. !   unsigned b5:1;
    14. !   unsigned b6:1;
    15. !   unsigned b7:1;
    16. !   unsigned b8:1;
    17. !  };
    18. ! }ser_data;
    19. ![index];
    20. 0x37: MOVF index, W
    21. 0x38: ADDLW 0x20
    22. 0x39: MOVWF FSR1L
    23. 0x3A: CLRF FSR1H
    24. 0x3B: MOVF INDF1, W
    25. 0x3C: MOVWF 0x79
    26. 0x3D: MOVF 0x79, W
    27. 0x3E: MOVWF ser_data
    28. !    
    29. !// CLK floating (input) + WPU=ON
    30. !// pulled low by slave -> interrupt on master
    31. !// master sets DAT to 1 if it wants to transfer data
    32. !// slave waits for CLK to go high
    33. !IO_RA0_serdat0_LAT=ser_data.b1;
    34. 0x3F: MOVF ser_data, W
    35. 0x40: ANDLW 0x1
    36. 0x41: MOVWF 0x79
    37. 0x42: RRF 0x79, W
    38. 0x43: BTFSS STATUS, 0x0
    39. 0x44: GOTO 0x48
    40. 0x45: MOVLB 0x2
    41. 0x46: BSF PORTA, 0x0
    42. 0x47: GOTO 0x4A
    43. 0x48: MOVLB 0x2
    44. 0x49: BCF PORTA, 0x0
    45. !IO_RA1_serclk0_LAT=1;
    46. 0x4A: BSF PORTA, 0x1
    47. !IO_RA0_serdat0_LAT=ser_data.b2;
    48. 0x4B: MOVLB 0x0
    49. 0x4C: RRF ser_data, W
    50. 0x4D: ANDLW 0x1
    51. 0x4E: MOVWF 0x79
    52. 0x4F: RRF 0x79, W
    53. 0x50: BTFSS STATUS, 0x0
    54. 0x51: GOTO 0x55
    55. 0x52: MOVLB 0x2
    56. 0x53: BSF PORTA, 0x0
    57. 0x54: GOTO 0x57
    58. 0x55: MOVLB 0x2
    59. 0x56: BCF PORTA, 0x0
    60. !IO_RA1_serclk0_LAT=0;
    61. 0x57: BCF PORTA, 0x1
    62. !IO_RA0_serdat0_LAT=ser_data.b3;
    63. 0x58: MOVLB 0x0
    64. 0x59: RRF ser_data, W
    65. 0x5A: RRF WREG, F
    66. 0x5B: ANDLW 0x1
    67. 0x5C: MOVWF 0x79
    68. 0x5D: RRF 0x79, W
    69. 0x5E: BTFSS STATUS, 0x0
    70. 0x5F: GOTO 0x63
    71. 0x60: MOVLB 0x2
    72. 0x61: BSF PORTA, 0x0
    73. 0x62: GOTO 0x65
    74. 0x63: MOVLB 0x2
    75. 0x64: BCF PORTA, 0x0
    I will change that to shifting and testing bit0, hope it requires less instructions.
  5. takao21203

    Thread Starter Distinguished Member

    Apr 28, 2012
    It is a little bit shorter but not much:

    Code ( (Unknown Language)):
    2. !;>>=1;
    3. 0x73: MOVLB 0x0
    4. 0x74: BTFSS ser_data, 0x0
    5. 0x75: GOTO 0x79
    6. 0x76: MOVLB 0x2
    7. 0x77: BSF PORTA, 0x0
    8. 0x78: GOTO 0x7B
    9. 0x79: MOVLB 0x2
    10. 0x7A: BCF PORTA, 0x0
    11. 0x7B: BCF STATUS, 0x0
    12. 0x7C: MOVLB 0x0
    13. 0x7D: RRF ser_data, F
  6. takao21203

    Thread Starter Distinguished Member

    Apr 28, 2012
    When I change it to a pointer variable, I get this:

    Code ( (Unknown Language)):
    2. !void send8bits(unsigned char index)
    3. 0x35: MOVLB 0x0
    4. 0x36: MOVWF index
    5. !{unsigned char ser_buf;
    6. ! unsigned char* ser_data=&ser_buf;
    7. 0x37: MOVLW 0x54
    8. 0x38: MOVWF 0x79
    9. 0x39: MOVF 0x79, W
    10. 0x3A: MOVWF ser_data
    11. ! *ser_data=spiReadBuffer[index];
    12. 0x3B: MOVF index, W
    13. 0x3C: ADDLW 0x20
    14. 0x3D: MOVWF FSR1L
    15. 0x3E: CLRF FSR1H
    16. 0x3F: MOVF INDF1, W
    17. 0x40: MOVWF 0x79
    18. 0x41: MOVF ser_data, W
    19. 0x42: MOVWF FSR1L
    20. 0x43: CLRF FSR1H
    21. 0x44: MOVF 0x79, W
    22. 0x45: MOVWF INDF1
    23. !    
    24. !// CLK floating (input) + WPU=ON
    25. !// pulled low by slave -> interrupt on master
    26. !// master sets DAT to 1 if it wants to transfer data
    27. !// slave waits for CLK to go high
    28. !IO_RA0_serdat0_LAT=(*ser_data)&1;*ser_data>>=1;
    29. 0x46: MOVF ser_data, W
    30. 0x47: MOVWF FSR1L
    31. 0x48: CLRF FSR1H
    32. 0x49: BTFSS INDF1, 0x0
    33. 0x4A: GOTO 0x4E
    34. 0x4B: MOVLB 0x2
    35. 0x4C: BSF PORTA, 0x0
    36. 0x4D: GOTO 0x50
    37. 0x4E: MOVLB 0x2
    38. 0x4F: BCF PORTA, 0x0
    39. 0x50: MOVLB 0x0
    40. 0x51: MOVF ser_data, W
    41. 0x52: MOVWF FSR1L
    42. 0x53: CLRF FSR1H
    43. 0x54: BCF STATUS, 0x0
    44. 0x55: RRF INDF1, F
    45. !IO_RA1_serclk0_LAT=1;
    46. 0x56: MOVLB 0x2
    47. 0x57: BSF PORTA, 0x1
    48. !/*IO_RA0_serdat0_LAT=*ser_data&1;>>=1;
    49. !IO_RA1_serclk0_LAT=0;
    It is lengthy because the generated code is reloading the FSRL all the time + also clearing FSRH.

    The correct value for FSRL is already set when I assign the address of the char to the pointer variable.

    Knowing the FSRL is set, I could use it in an assembler sequence, without the need for the MOVLB statements (leave permanently at 2 for PORTX), without the need to reload FSRL, and pre-setting the port bit, I only need one branch.

    This is the background for the post- to get some advice what is best.