Part Number:LAUNCHXL-F28069M
Hi,
After a bit of a battle with the McBSP and SPI modules to do DMA with the SPI 4 wire mode, eventually got it working. The SPI slave is a SSD1306 OLED display found here
The SSd1306 datasheet can be found here
There exists an Adafruit library for the Arduino as well
I have basic communication working with the SSD1306 and can do some basic drawing, although a bit quirky.
In this regard, have a question, if someone is able to help.
Attached my functional code:
/** * SPI DMA transfer to a SSD1306 OLED display * * The example transfers 8 bits of data internally with the Digital LoopBack(DLB) * with the McBSP module in SPI Master mode with 8 bit DMA support. * * The waveforms are observable on the following McBSP pins: * * SSD1306 McBSP GPIO Pin Signal * ------------------------------------------------- * RESET -> GPIO23 -> Pin#49 * D/C -> GPIO44 -> Pin#18 * SCLK -> MCLKX -> GPIO22 -> Pin#08 -> SPICLK * MOSI -> MDX -> GPIO20 -> Pin#45 -> SPISIMO * * By default for the McBSP examples, the McBSP sample rate generator (SRG) * input clock frequency is LSPCLK 80E6/4. */ #include "DSP28x_Project.h" #define LCDWIDTH 128 /* Cols 0 - 127 */ #define LCDHEIGHT 64 /* Rows 0 - 63 */ #define LAST_PAGE 7 /* Page end address */ #define LCD_PIXELS (LCDWIDTH * LCDHEIGHT) #define LCD_BYTES (LCD_PIXELS / 8) #define LAST_COL (LCDWIDTH) #define LAST_ROW (LCDHEIGHT) #pragma DATA_SECTION(rdata, "DMARAML5") /* buffer in DMA L5 SRAM */ Uint16 rdata[1024]; /* Received Data */ #pragma DATA_SECTION(sdata, "DMARAML5") /* buffer in DMA L5 SRAM */ Uint16 sdata[128]; /* Send Data */ #define SETLOWCOLUMN 0x00 #define EXTERNALVCC 0x01 #define SWITCHCAPVCC 0x02 #define SETHIGHCOLUMN 0x10 #define MEMORYMODE 0x20 #define COLUMNADDR 0x21 #define PAGEADDR 0x22 #define SCROLL_ENABLE 0x2F #define SCROLL_DISABLE 0x2E #define SCROLL_SET_VERT_AREA 0xA3 #define SCROLL_RIGHT_HORIZ 0x26 #define SCROLL_LEFT_HORIZONTAL 0x27 #define SCROLL_VERT_RIGHT_HORIZ 0x29 #define SCROLL_VERT_LEFT_HORIZ 0x2A #define SETSTARTLINE 0x40 #define SETCONTRAST 0x81 #define CHARGEPUMP 0x8D #define SEGREMAP 0xA0 #define SETSEGMENTREMAP 0xA1 #define DISPLAYALLON_RESUME 0xA4 #define DISPLAYALLON 0xA5 #define NORMALDISPLAY 0xA6 #define INVERTDISPLAY 0xA7 #define SETMULTIPLEX 0xA8 #define DISPLAYOFF 0xAE #define DISPLAYON 0xAF #define COMSCANINC 0xC0 #define COMSCANDEC 0xC8 #define SETDISPLAYOFFSET 0xD3 #define SETDISPLAYCLOCKDIV 0xD5 #define SETPRECHARGE 0xD9 #define SETCOMPINS 0xDA #define SETVCOMDETECT 0xDB #pragma DATA_SECTION(init, "DMARAML5"); /* dmatab in DMA L5 SRAM */ Uint16 init[] = { 0xae, /* DISPLAYOFF */ 0xd5, /* SETDISPLAYCLOCKDIV to 0x80 */ 0x80, 0xa8, /* SETMULTIPLEX */ LCDHEIGHT - 1, 0xd3, /* SETDISPLAYOFFSET to 0x00 */ 0x00, 0x40, /* line #0 | SETSTARTLINE */ 0x8d, /* CHARGEPUMP setup */ 0x14, 0x20, /* MEMORYMODE addressing */ 0x00, 0xa0 | 0x1, /* SEGREMAP */ 0xc8, /* COMSCANDEC */ 0xda, /* SETCOMPINS */ 0x12, 0x81, /* SETCONTRAST to 0xef */ 0xcf, 0xd9, /* SETPRECHARGE period */ 0xf1, 0xdb, /* SETVCOMDETECT level */ 0x40, 0xa4, /* DISPLAYALLON_RESUME */ 0xa6, /* NORMALDISPLAY */ 0x2e, /* SCROLL_DISABLE */ 0xaf, /* DISPLAYON */ }; #define BLACK 0 #define WHITE 1 #define INVERSE 2 typedef int int16_t; typedef int int8_t; typedef Uint8 uint8_t; typedef unsigned int uint16_t; typedef unsigned char uchar; volatile enum dmaio_state { IO_COMPLETE = 0, IO_PENDING = 1, } io_state; #define ssd1306_swap(a, b) { int16_t t = a; a = b; b = t; } #pragma DATA_SECTION(fb, "DMARAML5") /* buffer in DMA L5 SRAM */ Uint16 fb[1024]; /** * MCBSP_INIT_DELAY determines the amount of CPU cycles in the 2 sample rate * generator (SRG) cycles required for the Mcbsp initialization routine. * MCBSP_CLKG_DELAY determines the amount of CPU cycles in the 2 clock * generator (CLKG) cycles required for the Mcbsp initialization routine. */ #define CPU_SPD 80E6 #define MCBSP_SRG_FREQ CPU_SPD / 2 /* SRG input = LSPCLK (SYSCLKOUT/2) */ #define MCBSP_DELAY 2*(CPU_SPD/MCBSP_SRG_FREQ) /* CPU cycles in 2 SRG init delay */ void delay_loop(void) { long i; for (i = 0; i < MCBSP_DELAY; i++) { } /* must be at least 2 SRG cycles */ } void error(void) { __asm(" ESTOP0"); /* STOP on Error */ for (;;); } void init_mcbspa_gpio(void) { EALLOW; GpioCtrlRegs.GPAMUX2.bit.GPIO20 = 2; /* GPIO20=MDXA, data tx, MOSI */ GpioCtrlRegs.GPAMUX2.bit.GPIO22 = 2; /* GPIO22=CLKXA, clk tx, SCL */ GpioCtrlRegs.GPAQSEL2.bit.GPIO22 = 3; /* Asynch input GPIO22 (MCLKXA) */ EDIS; } void dma_xfer(Uint16 *src, Uint16 len) { io_state = IO_PENDING; /* dont allow concurrent transfers */ EALLOW; DmaRegs.DMACTRL.bit.HARDRESET = 1; __asm(" NOP"); /* Only 1 NOP needed per design */ /** * Channel 1, McBSPA Transmit */ DmaRegs.CH1.MODE.bit.CHINTE = 0; DmaRegs.CH1.BURST_SIZE.all = 0; /* 1 word/burst */ DmaRegs.CH1.SRC_BURST_STEP = 0; /* no effect with 1 word/burst */ DmaRegs.CH1.DST_BURST_STEP = 0; /* no effect with 1 word/burst */ DmaRegs.CH1.TRANSFER_SIZE = len; /* Interrupt after len */ DmaRegs.CH1.SRC_TRANSFER_STEP = 1; /* Increment address on every word */ DmaRegs.CH1.DST_TRANSFER_STEP = 0; /* Don't change detination */ DmaRegs.CH1.SRC_ADDR_SHADOW = (Uint32) src; /* Source is buffer */ DmaRegs.CH1.SRC_BEG_ADDR_SHADOW = (Uint32) src; /* Only for wrap fn */ DmaRegs.CH1.DST_ADDR_SHADOW = (Uint32) &McbspaRegs.DXR1.all; /* Dest. is McBSPA DXR */ DmaRegs.CH1.DST_BEG_ADDR_SHADOW = (Uint32) &McbspaRegs.DXR1.all;/* Only for wrap fn */ DmaRegs.CH1.CONTROL.bit.PERINTCLR = 1; /* Clear interrupt event flag */ DmaRegs.CH1.CONTROL.bit.ERRCLR = 1; /* Clear sync error flag */ DmaRegs.CH1.DST_WRAP_SIZE = 0xffff; /* Max len, no destination wrap */ DmaRegs.CH1.SRC_WRAP_SIZE = 0xffff; /* Max len, no source wrap */ DmaRegs.CH1.MODE.bit.CHINTE = 1; /* Enable channel interrupt */ DmaRegs.CH1.MODE.bit.CHINTMODE = 1; /* Interrupt at end of transfer */ DmaRegs.CH1.MODE.bit.PERINTE = 1; /* Enable peripheral interrupt */ DmaRegs.CH1.MODE.bit.PERINTSEL = DMA_MXEVTA; /* McBSPA peripheral, MXSYNCA int */ DmaRegs.CH1.CONTROL.bit.PERINTCLR = 1; /* Clear spurious interrupt flags */ /** * Channel 2, McBSPA Receive * We do really need to read, if we don't read the next SPI frame fails * Save reception buffer size by reading the burst into the same buffer */ DmaRegs.CH2.MODE.bit.CHINTE = 0; DmaRegs.CH2.BURST_SIZE.all = 0; /* 1 word/burst */ DmaRegs.CH2.SRC_BURST_STEP = 0; /* no effect with 1 word/burst */ DmaRegs.CH2.DST_BURST_STEP = 0; /* no effect with 1 word/burst */ DmaRegs.CH2.TRANSFER_SIZE = len; /* Interrupt after len */ DmaRegs.CH2.SRC_TRANSFER_STEP = 0; /* Don't move source address */ DmaRegs.CH2.DST_TRANSFER_STEP = 1; /* Don't move desination address */ DmaRegs.CH2.SRC_ADDR_SHADOW = (Uint32) &McbspaRegs.DRR1.all; /* Source is McBSPA DRR */ DmaRegs.CH2.SRC_BEG_ADDR_SHADOW = (Uint32) &McbspaRegs.DRR1.all;/* Only for wrap fn */ DmaRegs.CH2.DST_ADDR_SHADOW = (Uint32) &rdata[0]; /* Dest. is Buffer */ DmaRegs.CH2.DST_BEG_ADDR_SHADOW = (Uint32) &rdata[0]; /* Only for wrap fn */ DmaRegs.CH2.CONTROL.bit.PERINTCLR = 1; /* Clear interrupt event flag */ DmaRegs.CH2.CONTROL.bit.ERRCLR = 1; /* Clear sync error flag */ DmaRegs.CH2.DST_WRAP_SIZE = 0xFFFF; /* Max len, no destination wrap */ DmaRegs.CH2.SRC_WRAP_SIZE = 0xFFFF; /* Max len, no source wrap */ DmaRegs.CH2.MODE.bit.CHINTE = 1; /* Enable channel interrupt */ DmaRegs.CH2.MODE.bit.CHINTMODE = 1; /* Interrupt at end of transfer */ DmaRegs.CH2.MODE.bit.PERINTE = 1; /* Enable peripheral interrupt */ DmaRegs.CH2.MODE.bit.PERINTSEL = DMA_MREVTA; /* McBSPA peripheral, MRSYNCA int */ DmaRegs.CH2.CONTROL.bit.PERINTCLR = 1; /* Clear spurious interrupt flags */ /* start DMA */ DmaRegs.CH1.CONTROL.bit.RUN = 1; /* Start McBSPA DMA Transmission */ DmaRegs.CH2.CONTROL.bit.RUN = 1; /* Start McBSPA DMA Reception */ EDIS; /* init SPI peripheral */ McbspaRegs.SPCR2.all = 0x0000; /* Reset FS gen, SRG & Tx */ McbspaRegs.SPCR1.all = 0x0000; /* Reset Rx, Right justify, DLB dis */ McbspaRegs.PCR.all = 0x0F08; /* (CLKXM=CLKRM=FSXM=FSRM=1,FSXP=1) */ McbspaRegs.SPCR1.bit.DLB = 1; McbspaRegs.MFFINT.all = 0x0; /* Disable all interrupts */ McbspaRegs.SPCR1.bit.CLKSTP = 3; /* clock schema with CLKXP/CLKRP */ McbspaRegs.PCR.bit.CLKXP = 0; /* CPOL=0,CPHA=1,rise edge,no delay */ McbspaRegs.PCR.bit.CLKRP = 0; /* if CLKSTP=2, then CPHA=0 */ McbspaRegs.RCR2.bit.RDATDLY = 01; /* FSX setup time 1 = master mode */ McbspaRegs.XCR2.bit.XDATDLY = 01; /* FSX setup time 1 = master mode */ McbspaRegs.RCR1.bit.RWDLEN1 = 0; /* 5=32-bit word */ McbspaRegs.XCR1.bit.XWDLEN1 = 0; /* 5=32-bit word */ McbspaRegs.SRGR2.all = 0x2000; /* CLKSM=1, FPER=1 CLKG periods */ McbspaRegs.SRGR1.all = 0x000f; /* Frame Width=1, CLKGDV=16; 0x0f */ McbspaRegs.SPCR2.bit.GRST = 1; /* Enable the sample rate generator */ delay_loop(); /* Wait at least 2 SRG clock cycles */ McbspaRegs.SPCR2.bit.XRST = 1; /* Release TX from Reset */ McbspaRegs.SPCR1.bit.RRST = 1; /* Release RX from Reset */ McbspaRegs.SPCR2.bit.FRST = 1; /* Frame Sync Generator reset */ } void ssd1306_reset(void) { GpioDataRegs.GPACLEAR.bit.GPIO23 = 1; /* RESET = 0 */ DELAY_US(10000); /* wait 10ms */ GpioDataRegs.GPASET.bit.GPIO23 = 1; /* RESET = 1 */ DELAY_US(50000); /* wait 50mS */ } void ssd1306_init(void) { GpioDataRegs.GPBCLEAR.bit.GPIO44 = 1; /* D/C default LOW (CMD MODE) */ dma_xfer(init, sizeof(init)); /* SSD1306 Initialition */ } void setup_lcd_pins(void) { EALLOW; GpioCtrlRegs.GPAPUD.bit.GPIO23 = 1; /* disable pullup for RESET */ GpioCtrlRegs.GPBPUD.bit.GPIO44 = 1; /* disable pullup for D/C */ GpioCtrlRegs.GPAMUX2.bit.GPIO23 = 0; /* setup GPIO::RESET */ GpioCtrlRegs.GPBMUX1.bit.GPIO44 = 0; /* setup GPIO::D/C */ GpioCtrlRegs.GPADIR.bit.GPIO23 = 1; /* setup Output::Reset */ GpioCtrlRegs.GPBDIR.bit.GPIO44 = 1; /* setup Output::D/C */ EDIS; GpioDataRegs.GPASET.bit.GPIO22 = 1; /* RESET default HIGH (Release) */ } void lcd_fb_put(void) { while (io_state != IO_COMPLETE); /* wait for dma completion */ GpioDataRegs.GPBSET.bit.GPIO44 = 1; /* D/C default HIGH (DATA MODE) */ dma_xfer(fb, sizeof(fb)); /* SSD1306 Graphic data */ } void fb_clear(void) { Uint8 i; while (io_state != IO_COMPLETE); /* wait for dma completion */ for (i = 0; i < (LCDWIDTH * LCDHEIGHT / 8); i++) fb[i] = 0x00; } /** * Set window region for CGRAM access * void lcd_set_window(uint16_t c_start, * uint16_t p_start, * uint16_t c_stop, * uint16_t p_stop) * * @uint16_t c_start : COLUMN start address * @uint16_t p_start : PAGE start address * @uint16_t c_stop : COLUMN end address * @uint16_t p_stop : PAGE end address */ void lcd_set_window(uint16_t c_start, uint16_t p_start, uint16_t c_stop, uint16_t p_stop) { sdata[0] = COLUMNADDR; /* Set Column Address (0x21) */ sdata[1] = c_start; /* COL start; eg 0 */ sdata[2] = c_stop; /* COL end; max 'lcdwidth -1'; 127 */ sdata[3] = PAGEADDR; /* Set Page Address (0x22) */ sdata[4] = p_start; /* PAGE start; 0 */ sdata[5] = p_stop; /* PAGE end; max=7((lcdheight/8)-1) */ while (io_state != IO_COMPLETE); /* wait for dma completion */ GpioDataRegs.GPBCLEAR.bit.GPIO44 = 1; /* CMD mode */ dma_xfer(sdata, 5); /* transfer 6 bytes */ } /* INT7.1 is DMA Ch1 */ __interrupt void isr_dma_ch1(void) { EALLOW; DmaRegs.CH1.CONTROL.bit.HALT = 1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP7; /* Interrupt ACK */ EDIS; return; } /* INT7.2 is DMA Ch2 */ __interrupt void isr_dma_ch2(void) { EALLOW; DmaRegs.CH2.CONTROL.bit.HALT = 1; PieCtrlRegs.PIEACK.all = PIEACK_GROUP7; /* Interrupt ACK */ io_state = IO_COMPLETE; EDIS; return; } void main(void) { InitSysCtrl(); init_mcbspa_gpio(); DINT; InitPieCtrl(); IER = 0x0000; IFR = 0x0000; InitPieVectTable(); EALLOW; PieVectTable.DINTCH1 = &isr_dma_ch1; /* Tx ISR DMA CH1 INT7.1 */ PieVectTable.DINTCH2 = &isr_dma_ch2; /* Rx ISR DMA CH2 INT7.2 */ EDIS; setup_lcd_pins(); ssd1306_reset(); /* Reset LCD */ PieCtrlRegs.PIECTRL.bit.ENPIE = 1; /* Enable PIE block */ PieCtrlRegs.PIEIER7.bit.INTx1 = 1; /* Enable PIE Group 7 INT1(DMA CH1) */ PieCtrlRegs.PIEIER7.bit.INTx2 = 1; /* Enable PIE Group 7 INT2(DMA CH2) */ IER = 0x40; /* Enable CPU INT Group 7 */ EINT; /* Enable Global Interrupts */ ssd1306_init(); /* initialize LCD */ fb_clear(); /* clear fb array */ lcd_set_window(0, 0, 127, 7); /* col:0-127, page:0-7 access */ fb[0] = 0xff; /* draw 8 pixels @x=0, y=0-7 */ lcd_fb_put(); /* write to ssd1306 */ while (1) {} }
With the Adafruit library as well as seen with other code as well, the display window columns and page needs to be setup, where the data from the master needs to be written. When I do set that up that area, actually I am unable to see anything on the display. Actually the SSD1306 receives all the data, AFAICT. But, if I comment out the call to lcd_set_window(), I can see the 8 vertical pixels drawn exactly as expected. It is great that it is displayed as expected, But would need the set column address to work, for setting text position etc to work as expected. Any thought as to why this column setup would not work ?
Attached pic with the lcd_set_window() commented out:
Thanks,
Manu