Saturday 20 May 2017

ILI9325 Initialization code (16bit interface)

Here is a initialization code for ILI9325, which is a 320x240 LCD driver. The code was tested on STM32F10X.

    LCD_W_REG(0x07, 0);     // DTE=0, D[1:0]=00, GON=0
    LCD_W_REG(0x10, 0);     // BT[2:0]=0, SAP=0
    LCD_W_REG(0x11, 0);     // VC[2:0]=0
    LCD_W_REG(0x12, 0);     // PON=0, VRH[3:0]=0
    LCD_W_REG(0x13, 0);     // VDV[4:0]=0
    LCD_W_REG(0x29, 0x0);     // VCM[5:0]=0
    
    // DELAY 50MS
    GUI_Delay(50);
    
    LCD_W_REG(0x10, 0x0090);    // BT[2:0]=0, AP[2:0}=001, APE=1, SAP=0
    LCD_W_REG(0x11, 0x0);         // DC1[2:0]=000, DC0[2:0]=000
    LCD_W_REG(0x12, 0x0011);    // VCIRE=0, PON=1, VRH[3:0}=0001
    
    // DELAY 80MS
    GUI_Delay(80);
    LCD_W_REG(0x07, 0x0133);    // BASEE=1, DTE=1, GON=1, D[1:0]=11
    LCD_W_REG(0x10, 0x1090);    // SAP=1
    LCD_W_REG(0x2B, 0x000e);    // frame rate 112hz
    LCD_W_REG(0x0060, 0xA700); // Gate Scan Line
    LCD_W_REG(0x0061, 0x0001); // REV=1    

Wednesday 17 May 2017

ILI9341 initialization code for 16-bit interface

ILI9341 is a common LCD driver for 320x240 LCD displays. The graphic controller is pretty flexible, it can have serial or parallel interface, and the parallel interface can be 8, 9, or 16 bits wide. You can find handful of initialization codes for ILI9341 on the internet, some of them can be long and complicated. Here I provide a very simple one, it's for the 16-bit interface, and only has less than 10 lines of code.
It was tested on STM32F103 with external bus (SFMC)

#define LCD_ILI9341_CMD(index)       ((*(volatile u16 *) ((u32)0x6C000000)) = ((u16)index))
#define LCD_ILI9341_Parameter(val)   ((*(volatile u16 *) ((u32)0x6D000000)) = ((u16)(val)))


volatile u32 i;

GPIOG->BRR = LCD_RESET;          //reset lcd
for (i=0; i< (0xAFFf<<2); i++);  // delay
GPIOG->BSRR = LCD_RESET;
for (i=0; i< (0xAFFf<<2); i++); 
        
/*  Pixel Format Set (3Ah)  */
LCD_ILI9341_CMD(0x3a); 
LCD_ILI9341_Parameter(0x55);    // set to 16bit per pixel
        
/* Sleep Out (11h)  */
LCD_ILI9341_CMD(0x11);
for (i=0; i< (0xAFFf<<2); i++);

/* Display ON (29h) */
LCD_ILI9341_CMD(0x29);

Saturday 13 May 2017

Accessing USB buffer memory in STM32F10x

The USB device peripheral in STM32F10x seems not very well integrated in the chip like other parts. The USB related registers are even not defined in the header file stm32f10x.h , to programme with the USB, you have to define these registers by yourself.

Of course, you can use the factory provided peripheral library, and further more you can use the USB device stack which comes with the STM32CubeMX. But I like to do things in the way that writing and reading the registers directly. To me understanding other people's code is even harder than writing my own.

The job was done on a STM32F107, which has a dedicated 512 bytes buffer memory for the USB port. But this memory is not like the normal memory that you can access it with a simple read or write. In the reference manual, there's few words about it, user is only told the memory is "structured as 256 words by 16 bits", "all packet memory locations are accessed by the APB using 32-bit aligned addresses", and "the actual memory location address must be multiplied by two". It's very vague and the information is scattered in different places in the manual.

It cost me quite a while to figure out how to access the USB buffer memory. Let's get to the conclusion first:

  1. Don't use byte-width access, use 16bit, even if you just want to write 1 byte.
  2. The address in the buffer should be doubled
  3. When write a sequence of data, increase the buffer address double as the outside RAM.

If the address you are accessing is 8 in the USB buffer, or in another word you are accessing the 8th byte in the buffer, you have to write or read at BUFFER_BASE_ADDRESS+16.

In the USB device enumeration process, the micro-controller may need to send any amount of data from 1 byte to hundreds. But even if you just need to write 1 byte to the buffer, you need to use 16-bit access to write a 16bit word.

Here is my code to copy data to the USB buffer. DestAddr is the buffer address

    uint16_t *SourceAddr, *DestAddr;

    for (i=0; i<(BytesToMove+1)/2; i++)   // copy data to buffer
    {
        *DestAddr = *SourceAddr++;
        DestAddr += 2;
    }


Tuesday 9 May 2017

Debugging STM32F10X codes in RAM using Keil MDK

I'm using STM32F103ZE recently, it has 512KB flash and 64KB RAM, for a small program, the 64K RAM is big enough to hold both the code and the data. If we can debug codes in RAM, then there's no need to write the flash every time, so you don't have to worry about the life of the flash.

STM32F10X chips have 3 boot mode: flash, bootloader and RAM, depends on the levels on Boot0 and Boot1 pins. But the Boot From RAM mode is actually not very useful, because there's no mapping mechanism in the chip to let to CPU to access RAM area at reset. We know that in STM32F10x chips, flash memory starts from address 0x800 0000, and bootloader resides from 0x1FFF B000 or 0x1FFF F000, and in the respective boot mode, both of the addresses are mapped to 0x0000 0000, so the CPU can get the right place (the CPU loads stack pointer SP and program pointer PC from address 0x0000 0000 and 0x0000 0004 at reset).

But for RAM, it can not be mapped to address 0, so there's no strait way to get the CPU to boot from RAM. We need the help from the debugger to load the SP and PC, later in this article I will show you how to do it.


I wrote a simple program to do the RAM demo, it just flashes a LED at PB0:

#define LED1 0x0001 // PB0

int main(void)
{
volatile unsigned long i;
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
GPIOB->CRL = 0x44444442;
while(1)
{
GPIOB->ODR ^= LED1;
for (i=0; i<0x100000; i++);
}
return 0;

}

To let the code loaded in RAM, we need to do some modifications with the project options.

First, In the Keil uVision IDE, select the project settings from Project →Options for Target 'Target1' :



The high-lighted area are the settings need to be modified. We change to IROM1 address to start from the RAM base address, and divide the whole RAM in 2 parts, half for code, and half for data.

Step 2, we go to the 'Debug' tab and make the following changes:


You probably have already noticed the RAM.ini file. What is this? This is the most important part in this project to make the RAM debugging successful. The Initialisation File is a command set for the debugger hardware, these commands are executed at the beginning of debug. Keil provided it with it's MDK package, you can find it here:

\ARM\Boards\ST\STM3210E-EVAL\Blinky\Dbg_RAM.ini

The file is short and the code is strati forward, it basically just load the SP and PC from the start of the RAM and setup the vector table:

FUNC void Setup (void) {
SP = _RDWORD(0x20000000); // Setup Stack Pointer
PC = _RDWORD(0x20000004); // Setup Program Counter
_WDWORD(0xE000ED08, 0x20000000); // Setup Vector Table Offset Register
}


load %L incremental

Setup(); // Setup for Running


g, main

Final step, to modify the programming algorithm. Hit the “settings” in the “Debug” tab, then select “Flash Download” tab.


First of all, select the “Do not Erase” option, as we are using RAM, then change the address range to the RAM area, otherwise you will be prompted a “no algorithm for address xxxxxxxxx” error when downloading the code. Finally, don't forget to change the “RAM for Algorithm” to avoid overlaps with the code area.

Now we are all set to run our code in RAM! Make the project and then enter the debug widow, and hit 'Run'. Yeah, the LED is flashing, our code is running in RAM! The BOOT0 and BOOT1 pins are actually not having effect on the running, you can have any settings on these 2 pins when debugging.

Some issues with the RAM debugging:

1. Code and Data size are limited.
2. If you reset the chip, you have to run the Setup() function in the debugger initialization file manually, otherwise the CPU will not get the correct SP and PC. To do so, in the command window, type “Setup()” in the command line and press ENTRE.