
This post is an adaptation of my running log that I kept when I was developing a driver for the BeagleBone Black that allowed it to communicate with a Sitronix ST7036 Dot Matrix LCD Controller/Driver. Basically, we had to write software in ARM assembly that would communicate with the LCD screen using UART.
My source code can be found here:
The reference figures are from:
AM335x Sitara™ Processors Technical Reference Manual
The BeagleBone Black System Reference Manual
NHD‐C0220BiZ‐FSW‐FBW‐3V3M
COG (Chip‐On‐Glass) Character Liquid Crystal Display Module
Sitronix ST7036 Dot Matrix LCD Controller/Driver
Note:
Bold text means that the text came from the original assignment/proposal
Grey background text came from the AM335x Sitara™ Processors Technical Reference Manual
In this log, figures will generally precede the text/steps that use them.
Prompt
1) Read through the Hall Ch. 7 section on I2C to get an overview of SCL and SDA signals, Start condition, Stop condition, ACK, etc.
2) Consider some of the high level initialization you need to do:
a) Take a look at the attached pin diagram for the P9 connector on the BBB to find the pins identified as I2C1_SCL and I2C1_SDA. Note that the default pin for Pin-17 is the spi0_cs0 signal, so you will have to find the conf_spi0_cs0 register in the Control Module and change the mode to connect the I2C1_SCL signal to pin 17. You will need to repeat this process to connect the I2C1_SDA signal to pin 18.





From the attached table, Table 13 (fig. 1), and also from the TI MUX (fig. 2) utility pin-17 must be placed in MODE2 for I2C1_SCL and pin-18 must also be placed in MODE2 for I2C1_SDA. From the AM335x Sitara Technical Reference Manual the base address to the control module is 0x44E10000 (fig. 3). The offset for conf_spi0_d1 is 0x958 and the offset for conf_spi0_cs0 is 0x95C (fig4). To switch to MODE2 a 0x02 must be written to bits 0-2 at these offsets (fig. 5).
Prompt
2. b. Think about how you turn on the clock for the I2C1 module with the appropriate register in the Clock Module, as you have needed to do before with the Timer and GPIO.



From the AM335x Sitara Technical Reference Manual the CM_PER register has a base address of 0x44E00000 (fig. 6), the CM_PER_I2C1_CLKCTRL register has an offset of 0x48 (fig. 7). A word 0x02 must be written to bits 0-1 to explicitly enable the clock (fig. 8).
Prompt
3. Read through the I2C Chapter in the Sitara manual. Study Figure 21-8 to give you another view of the I2C waveforms for the 7-bit addressing mode you will be using. Pay attention specifically to section 21.3.15 to help you make a high level list of the steps required to initialize the I2C controller. Note how to get the system’s 48MHz clock scaled down to 12MHz and how to get a 100Kbps SCL for standard mode operation.1 Also note that you will be using polled mode transmit as is illustrated in the text example instead of interrupt based transmission, and that you will not be using the DMA capability due to the small number of bytes to transfer.
Note 1: F/S stands for Fast/Standard mode and HS stands for High Speed mode.
4. Carefully read sections 21.3.15.3, 21.3.15.4, and 21.3.15.6.
5. Read through the Register list in Table 218 and mark any mentioned in the previous sections of this walkthrough. Then skim through the descriptions for the registers you marked and find the specified bit patterns needed to initialize the controller, start a transmission, and transfer the next byte when the controller is ready. It can be helpful to print out copies of the descriptions so you can write notes/patterns on them and include them in your design log.
From the Manual:
21.3.15 How to Program I2C
21.3.15.1 Module Configuration Before Enabling the Module
1. Program the prescaler to obtain an approximately 12-MHz I2C module clock (I2C_PSC = x; this value is to be calculated and is dependent on the System clock frequency).
2. Program the I2C clock to obtain 100 Kbps or 400 Kbps (SCLL = x and SCLH = x; these values are to be calculated and are dependent on the System clock frequency).
3. Configure its own address (I2C_OA = x) – only in case of I2C operating mode (F/S mode).
4. Take the I2C module out of reset (I2C_CON:I2C_EN = 1).
21.3.15.2 Initialization Procedure
1. Configure the I2C mode register (I2C_CON) bits.
2. Enable interrupt masks (I2C_IRQENABLE_SET), if using interrupt for transmit/receive data.
3. Enable the DMA (I2C_BUF and I2C_DMA/RX/TX/ENABLE_SET) and program the DMA controller) – only in case of I2C operating mode (F/S mode), if using DMA for transmit/receive data.
21.3.15.3 Configure Slave Address and DATA Counter Registers
In master mode, configure the slave address (I2C_SA = x) and the number of byte associated with the transfer (I2C_CNT = x).
21.3.15.4 Initiate a Transfer
Poll the bus busy (BB) bit in the I2C status register (I2C_IRQSTATUS_RAW). If it is cleared to 0 (bus not busy), configure START/STOP (I2C_CON: STT / I2C_CON: STP condition to initiate a transfer) – only in case of I2C operating mode (F/S mode).
21.3.15.5 Receive Data
Poll the receive data ready interrupt flag bit (RRDY) in the I2C status register (I2C_IRQSTATUS_RAW), use the RRDY interrupt (I2C_IRQENABLE_SET.RRDY_IE set) or use the DMA RX (I2C_BUF.RDMA_EN set together with I2C_DMARXENABLE_SET) to read the received data in the data receive register (I2C_DATA). Use draining feature (I2C_IRQSTATUS_RAW.RDR enabled by I2C_IRQENABLE_SET.RDR_IE)) if the transfer length is not equal with FIFO threshold.
21.3.15.6 Transmit Data
Poll the transmit data ready interrupt flag bit (XRDY) in the I2C status register (I2C_IRQSTATUS_RAW), use the XRDY interrupt (I2C_IRQENABLE_SET.XRDY_IE set) or use the DMA TX (I2C_BUF.XDMA_EN set together with I2C_DMATXENABLE_SET) to write data into the data transmit register (I2C_DATA). Use draining feature (I2C_IRQSTATUS_RAW.XDR enabled by I2C_IRQENABLE_SET.XDR_IE)) if the transfer length is not equal with FIFO threshold.
asds




The waveform for 7-bit addressing mode is shown in fig. 9. The manual expresses the following good advice:
All bits defined as reserved must be written by software with 0s, for preserving future compatibility. When read, any reserved bit returns 0. Also, note that it is good software practice to use complete mask patterns for setting or testing individually bit fields within a register.
21.3.15.1 Step 1
From the memory map the base address for the I2C registers is 0x4802A000 (fig. 10) and the offset for the I2C Clock Prescaler Register (I2C_PSC) is 0xB0 (fig. 11). To scale the 48MHz clock to 12MHz we need to divide by 4; writing 0x04 to bits 0-7 in the I2C_PSC register will do (fig. 11).


21.3.15.1 Step 2
Using Standard mode (F/S) I seek the values to set the data rate to 100Kbps, using a 50% duty cycle. The I2C_SCLL register, offset 0xB4, (fig. 13) shows the equation needed for the SCL low time value, SCLL (fig. 13). The I2C_SCLH register, offset 0xB8 (Fig. 14) shows the equation for SCLH. These values (53D=0x35 for low and 55D=0x37 for high) will be written to bits 0-7 in their respective registers.



21.3.15.1 Step 3
I’m using Standard mode so I’ll have to configure I2C’s own address. The I2C_OA register, offset 0xA8 (fig. 15), is where I’ll send the appropriate word. I’m using 7-bit addressing and the register in fig. 15 says that bits 0-6 will hold the address when I2C_CON[7] is cleared to 0 and that bits 7-9 of I2C_OA must be cleared to 000 by application software. I think that I need to clear bit-7 of I2C_CON first. The I2C_CON register has an offset of 0xA4 (fig. 16). Performing a BIC should work with value 0x80 for bit-7. Then I’ll clear bits 7-9 in the I2C_OA register with a BIC using value 0x380. It looks like the own address can be anything I choose, so I’ll choose 0x7F and write this to bits 0-6 of the I2C_OA register.

21.3.15.1 Step 4
Next, take the I2C module out of reset (fig. 17). Writing 0x8000 to the I2C_CON register will set the I2C_EN bit (bit -15) and do this.
21.3.15.2 Step 1
I need to configure the I2C mode register bits at in I2C_CON at offset 0xA4. Bits 16-31 are reserved, bit-15 needs to be set to enable, bit-14 is reserved, bit 12-13 need to be cleared for I2C Fast/Standard mode, I’ll clear bit-11 during config, bit-10 should be set for master mode, bit-9 should be set for Transmitter mode, bits 4-8 should be clear for 7-bit address mode, bits 2-3 are reserved, and bits 0-1 should be clear for no action for the Stop condition and Start condition. On bit-11 a note says: “See the Philips I2C spec for more details [1].” I’ll try clearing this bit first. This results in a word 0x8600 to be written to the I2C_CON register.
21.3.15.2 Step 2/3
Not applicable because I’m using polling, not interrupts, and I’m not using DMA.


21.3.15.3 Step 1
I need to configure a slave address and the number of bytes associated with the transfer. Fig. 18 shows the I2C_SA register at offset 0xAC. Bits 0-9 should be written with the slave address. I’m not quite sure what to write here. Should I write an arbitrary address? How would I set addresses to multiple slaves on the same lines? For now, I’ll try to write an arbitrary address of 0x01 this.
-Correction: From the ST7036 tech manual: “Four 7-bit slave addresses (0111100 to 0111111) are reserved for the ST7036.” I’ll use address 0x0111100.
The I2C_CNT register, offset 0x98 (fig. 19), shows that I need to write a number here for how many bytes I intend to transfer. There are 2 lines of 20 characters so I’ll write a 20D or 0x14 here.
July 29, 2015
Today, I’ll focus on making data transfer with the LCD module.
Prompt
6. Construct your high-level algorithm and then your low-level algorithm for the program sections up to this point. This much is basically generic for communicating with any I2C device on a polled basis. Note that the LCD I2C interface for the LCD Module does not allow reading of LCD controller registers. (You can only write to them)
High-level Algorithm – Transfer Data
- Master pulls SDA low while line is high to create a START condition
- Master pulses SCL line and shifts data out on SDA synchronously (MSB first)
- After 8 bits transferred
- If slave received 8 bits correctly
- Slave pulls SCL low synchronously
- If slave needs time to process, it holds the SCL line low, Master inserts wait states
- When finished slave releases SCL line, line is pulled high
- Master can send next byte
- If slave doesn’t receive 8 bits correctly two things can happen
- Master can generate a STOP condition by allowing the SDA line to transition to high while the SCL line is high, or
- Master can initiate another START condition
- If slave received 8 bits correctly
Note: this is an I2C algorithm, not necessarily what I’ll be doing to transfer data.
21.3.15.4 Step 1
To initiate a transfer I need to poll the busy bit (bit-12) in the I2C_IRQSTATUS_RAW register at offset 0x24 (fig. 20). Testing 0x1000 at 0x4802A024 will do this. If the busy bit (BB) is clear then the bus is not busy and I need to configure START/STOP in the I2C_CON (Fig. 21), offset 0xA4, which is bit-1 (STP). Writing a 0x02 to the I2C_CON register will initiate a STOP condition. Writing a 0x01 to the I2C_CON register will initiate a START condition.
21.3.15.6 Step 1
To transmit data the ready interrupt flag bit must be polled. This is in the I2C_IRQSTATUS_RAW register at offset 0x24 (fig. 24) and the bit is XRDY, bit-4. Testing a 0x10 against the register will indicate that the interrupt is ready. When this is the case I’ll need to set the XRDY_IE bit, bit-14, on the I2C_IRQENABLE_SET register, offset 0x2C, by writing a 0x400 to the register (fig. 22). Next I’ll need to write a byte to the I2C_DATA register, offset 0x9C (Fig. 23). I’ll then poll the busy bit (BB) and load another byte into I2C_DATA when it’s ready.





July 31, 2015
Here is a recap of the initialization steps:
Initialize BBB
- Change pin modes
- I2C1_SCL (pin-17) -> MODE2
- Write 0x02 (Mode, 2) to 0x44E1095C (control module+conf_spi0_cs0)
- I2C1_SDA (pin-18) ->MODE2
- Write 0x02 (Mode, 2) to 0x44E10958 (control module+conf_spi0_d)
- I2C1_SCL (pin-17) -> MODE2
- Turn on I2C clock
- Write 0x02 (MODULEMODE, Enable) to 0x44E00048 (CM_PER+CM_PER_I2C1_CLKCTRL)
- Scale I2C clock to 12MHz
- Write 0x04 (bits 0-7/PSC, /4) to 0x4802A0B0 (I2C+I2C_PSC)
- Set data rate to 100Kbps, 50% duty cycle
- Write 0x35(bits 0-7/SCLL/53D) to 0x4802A0B4 (I2C+I2C_SCLL)
- Write 0x37(bits 0-7/SCLH/55D) to 0x4802A0B8 (I2C+I2C_SCLH)
- Configure I2C’s own address
- Clear bit-7 of I2C_CON
- BIC 0x80 (bit-7/XOA0) at
- (I2C+I2C_CON)
- Clear bits 7-9 in I2C_OA
- BIC 0x380 (bits 7-9/OA) at 0x4802A0A8 (I2C+I2C_OA)
- Set address in I2C_OA
- Write 0x7F (bits 0-6/OA) to 0x4802A0A8 (I2C+I2C_OA)
- Clear bit-7 of I2C_CON
- Take I2C module out of reset
- Set bit-15 in I2C_EN
- Write 0x8000 (bit-15/I2C_EN) to 0x4802A0A4 (I2C+I2C_CON)
- Set bit-15 in I2C_EN
- Set as master, transmitter mode, 8-bit data
- Set bits 9 and 10
- Write 0x8600 (bits 9, 10, 15) to 0x4802A0A4 (I2C+I2C_CON)
- Set bits 9 and 10
- Set slave address to 01111002 (0x36)
- Write 0x36(bits 0-6/SA/01111002) to 0x4802A0AC (I2C+I2C_SA)
- Set 8-bit data transfer mode
- Write 0x14 (bits 0-15/DCOUNT/20 characters) to 0x4802A098 (I2C+I2C_CNT)
August 3, 2015
Here is a recap of the transfer steps:
Transfer Data
- Poll I2C_IRQSTATUS_RAW[12] to see when bus is free
- Test 0x1000 (bit-12/BB) against 0x4802A024 (I2C+I2C_IRQSTATUS_RAW)
- When bus clear: Initiate a START condition
- Write 0x601 (bit-0/STT, bit-9/TRX, bit-10/MST) to 0x4802A0A4 (I2C+I2C_CON)
- Poll I2C_IRQSTATUS_RAW[8] to see when bus is free
- Test 0x100 (bit-8/BF) against 0x4802A024 (I2C+I2C_IRQSTATUS_RAW)
- ?
- Poll interrupt ready flag
- Test 0x10 (bit-4/XRDY) against 0x4802A024 (I2C+I2C_IRQSTATUS_RAW)
- When XRDY set: Set XDRY_IE bit in I2C_IRQENABLE_SET
- Write 0x400 (bit-14/XRDY_IE) to 0x4802A02C (I2C+I2C_IRQENABLE_SET)
- Fetch next char/inst
- Point to I2C_MESSAGE, post-increment
- Get current value
- Write character to I2C_DATA
- Write char/inst to 0x4802A09C (I2C+I2C_DATA)
- Loop
21.3.16 I2C Behavior During Emulation
From the manual it looks like I’ll need to set up the emulation environment to make breakpoints work during debugging. I’ll need to disable this code when I’m done testing. I need to clear bit-14 (FREE) in the I2C_SYSTEST register at offset 0xBC (fig. 26) I’ll do this by BIC-ing a value of 0x4000 to this register.
I’ll also need to set the appropriate suspend register as described in Section 27.1.1.1. In the manual table 27-3 shows the DebugSS_DRM registers used for picking a peripheral to disable during breakpoints. The DebugSS_DRM register has the base address 0x4B160000 (Fig. 25). I2C-1 is controlled by the I2C_1_Suspend_Control register at offset 0x22C. I guess I write a 0x01 to 0x4B16022C to set this register.


August 5, 2015
Prompt
7. Carefully study the manual for the LCD module and determine the bytes you have to send it to get it in the desired mode, and determine how you send the characters you want displayed. Make a list of the required initialization words and the words needed to make it display the message. You will need to use both rows, but the displays have lots of other capabilities so feel free to get all fancy.
I’ll be working on the steps needed to initialize the LCD module. In the ST7036 manual, it lists the steps performed by the LCD module upon power up (Fig. 21). It looks like I’ll need to set it to 2-line display, turn on the display, and turn on the cursor, at least. Here are some commands I’ll send to it.

From the ST7036 manual: During read or write operation, two 8-bit registers are used. One is data register (DR), the other is instruction register (IR) (Fig. 28). The instruction register can only be written to. To select the register to be used, the RS input pin is used. Fig. 29 shows a template for communication with the board. A START bit begins the string of bits, followed by the slave address. A bit is next, then an ACK from the ST7036i. This is where I’ll send commands as control bytes. A control byte starts with a Co byte, which indicates whether more control bytes will follow or only data bytes. The RS bit is next (Fig. 28), which controls whether data or control bytes are to be read or written. Six bits follow which specify the command. I’ll write a ‘1’ to the continuation bit, Co, for the commands until I know I won’t send any more.



| Char | b0-b3 | b4-b7 | Char | b0-b3 | b4-b7 | |
| C | 0011 | 0100 | E | 0101 | 0100 | |
| a | 0001 | 0110 | C | 0011 | 0100 | |
| s | 0011 | 0111 | E | 0101 | 0100 | |
| i | 1001 | 0110 | 0000 | 0010 | ||
| m | 1101 | 0110 | 3 | 0011 | 0011 | |
| i | 1001 | 0110 | 7 | 0111 | 0011 | |
| r | 0010 | 0111 | 2 | 0010 | 0011 | |
| 0000 | 0010 | : | 1010 | 0011 | ||
| S | 0011 | 0101 | 0000 | 0010 | ||
| o | 1111 | 0110 | S | 0011 | 0101 | |
| w | 0111 | 0111 | u | 0101 | 0111 | |
| i | 1001 | 0110 | m | 1101 | 0110 | |
| n | 1110 | 0110 | m | 1101 | 0110 | |
| s | 0011 | 0111 | e | 0101 | 0110 | |
| k | 1011 | 0110 | r | 0010 | 0111 | |
| i | 1001 | 0110 | 0000 | 0010 | ||
| 0000 | 0010 | 2 | 0010 | 0011 | ||
| 0000 | 0010 | 0 | 0000 | 0011 | ||
| 0000 | 0010 | 1 | 0001 | 0011 | ||
| 0000 | 0010 | 5 | 0101 | 0011 |

Figure 30
August 6, 2015
Prompt
8. Complete your high-level and low-level algorithms using the information acquired in step 7, and translate your finished algorithms into assembly language.
I translated my algorithms into assembly language, but it didn’t work. After fixing a couple of dumb mistakes, I found I got a little progress. When looking at the I2C_IRQSTATUS_RAW register when stepping through the code, I noticed something weird. I was polling the register to look for the busy bit, bit-12, to be set. It wasn’t, but I was getting a value of 0x80, which corresponded to bit-7 being set. This is the AERR bit, which seems to mean that either I was attempting to write to the I2C_DATA register while the FIFO register was full, or that I was reading from it while it was empty. At this point, I hadn’t touched the I2C_DATA register, though. After posting about it on the forum, the TA advised me to perform a software reset on the I2C system first. After looking through the registers, I found that you can do this with the I2C_CON register. Bit-15, I2C_IE, is used to enable the I2C module, but when you clear this bit, it will also reset the module, clear the transmit FIFO, and clear all status bits. I’ll add this step to the assembly before the steps that set it to master mode, etc.
The TA also advised clearing all interrupts. The I2C_IRQENABLE_CLR register at offset 0x30 is where you do this. Bits 12, and 15-31 are reserved, but the rest of the bits should be set to enable their respective interrupts. This results in a value of 0x6FFF. This is too big a value to use a MOV command. To get around this, I’ll make a single element array and place the value in it and simply read from it. I wrote this into the assembly, and it seemed to work; the register read 0x0 after writing 0x6FFF to it. After this, I will need to set the BF interrupt and the XRDY interrupt. Writing a 0x110 to I2C_IRQENABLE_SET register at offset 0x2C will set bit-8/BF_IE and bit-4/XRDY_IE.
When going through some printouts of the registers I was using, I found that I was using the wrong value to the clock prescaler. I didn’t read it closely enough and naively wrote a 0x04 to divide by 4. This is an “off-by-one” type situation. The correct value is 0x03.
I just found a section about software reset in the main manual. It describes a software reset method that uses the I2C_SYSC register at offset 0x10. It says to set the SRST bit, bit-1, to perform a software reset. Writing a value of 0x02 to this register will do this.
I found another possible mistake. To initiate a START condition, I was simply setting the I2C_CON[0], or bit-0/STT in the configuration register. When looking at the I2C_IRQSTATUS_RAW register, I saw a note in the BB field that says that to initiate a START condition, you must set the MST, TRX, and STT bit, not just the STT. This results in a value of 0x601 that must be written to the I2C_CON register. I did do this previously in the initialization steps, but not when I explicitly set a START condition.
21.3.14.2 FIFO Polling Mode Operation
In FIFO polled mode (I2C_IRQENABLE_SET.XRDY_IE and I2C_IRQENABLE_SET.RRDY_IE disabled and DMA disabled), the status of the module (receiver or transmitter) can be checked by polling the XRDY and RRDY status registers (I2C_IRQSTATUS_RAW) (RDR and XDR can also be polled if draining feature must be used). The XRDY and RRDY flags are accurately reflecting the interrupt conditions mentioned in Interrupt Mode. This mode is an alternative to the FIFO interrupt mode of operation, where the status of the receiver and transmitter is automatically known by means of interrupts sent to the CPU.
August 12, 2015
| Char | b0-b3 | b4-b7 | Char | b0-b3 | b4-b7 | |
| C | 0011 | 0100 | E | 0101 | 0100 | |
| a | 0001 | 0110 | C | 0011 | 0100 | |
| s | 0011 | 0111 | E | 0101 | 0100 | |
| i | 1001 | 0110 | 0000 | 0010 | ||
| m | 1101 | 0110 | 3 | 0011 | 0011 | |
| i | 1001 | 0110 | 7 | 0111 | 0011 | |
| r | 0010 | 0111 | 2 | 0010 | 0011 | |
| 0000 | 0010 | : | 1010 | 0011 | ||
| S | 0011 | 0101 | 0000 | 0010 | ||
| o | 1111 | 0110 | S | 0011 | 0101 | |
| w | 0111 | 0111 | u | 0101 | 0111 | |
| i | 1001 | 0110 | m | 1101 | 0110 | |
| n | 1110 | 0110 | m | 1101 | 0110 | |
| s | 0011 | 0111 | e | 0101 | 0110 | |
| k | 1011 | 0110 | r | 0010 | 0111 | |
| i | 1001 | 0110 | 0000 | 0010 | ||
| 0000 | 0010 | 2 | 0010 | 0011 | ||
| 0000 | 0010 | 0 | 0000 | 0011 | ||
| 0000 | 0010 | 1 | 0001 | 0011 | ||
| 0000 | 0010 | 5 | 0101 | 0011 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
| 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
Transfer Data
- Poll I2C_IRQSTATUS_RAW[12] to see when bus is free
- Test 0x1000 (bit-12/BB) against 0x4802A024 (I2C+I2C_IRQSTATUS_RAW)
- When bus clear: Initiate a START condition
- Write 0x601 (bit-0/STT, bit-9/TRX, bit-10/MST) to 0x4802A0A4 (I2C+I2C_CON)
- Poll I2C_IRQSTATUS_RAW[4] to see when ready for data
- Test 0x10 (bit-4/XRDY) against 0x4802A024 (I2C+I2C_IRQSTATUS_RAW)
- Fetch next char/inst
- Point to I2C_MESSAGE, post-increment
- Get current value
- Write character to I2C_DATA
- Write char/inst to 0x4802A09C (I2C+I2C_DATA)
- Loop to 3a..