Thursday 7 December 2017

CSR1010 Bluetooth Low Energy (BLE) Demo With Source

There are many Bluetooth Low Energy chips on the market, by a chance I got a CSR development system and did some experiments with it.

CSR1010 is a BLE SoC and the development system is based on C. You don't need to deal with the low level Bluetooth protocols, all you need to do is creating your own service and using the APIs.

To be honest, the CSR development system has only very limited debugging abilities, you can somewhat debug your code doing such as 'stepping' and set break point, but there's no way you can see the values of your variables, so it makes little sense to do the debugging.

Fortunately, my code runs and it seems OK.

Let's describe the functionalities of this simple demo first, besides the basic mandatory BLE services, there is only one user service created, it has 2 characteristics, one is feeding a random data of 1 byte to the client, when the notification is turned on, the Bluetooth board will push the data to the mobile phone every 2 seconds. And the another one is 24-bit and has both read and write properties, this data is used to control the tri-color LED on board. The MSB byte is for the red LED, the LSB is used to control the blue. The number controls the duty cycle of the PWM, 0 is OFF, and set to 0xff you get 100% duty cycle the LED will be in full brightness.

The user service is created like this:

#ifndef JJ_TEST_SERVICE_H
#define JJ_TEST_SERVICE_H

primary_service
{
    uuid : 333f5834-123e-4f2b-9da8-8e863c322e5f,
    name : "JJ_test_service",
    
    characteristic
    {
        uuid : e8069f93-c150-48ef-a02d-589b652e9538,
        name : "RANDOM_NUMBER",
        flags : [FLAG_IRQ, FLAG_ENCR_R],
        properties : [read, notify],
        value : 0x00,
        client_config
        {
            flags : [FLAG_IRQ],
            name : "RANDOM_NUMBER_C_CFG"
        }
    },
    
    characteristic
    {
        uuid : d1dea016-a7fb-4dfa-84c5-b24158669e20,
        name : "LIGHT_CTL",
        flags : [FLAG_IRQ, FLAG_ENCR_R],
        properties : [read, write],
        size_value : 3
    }
}


#endif

This demo is based on the CSR demo application "gatt_server", as the whole source code is too much, I only include the user created part here.

JJ_test_service.c:

/*============================================================================*
 *  SDK Header Files
 *===========================================================================*/

#include <gatt.h>           /* GATT application interface */
#include <buf_utils.h>      /* Buffer functions */
#include <random.h>
#include <pio.h>


/*============================================================================*
 *  Local Header Files
 *===========================================================================*/

#include "gatt_server.h"    /* Definitions used throughout the GATT server */
#include "app_gatt_db.h"    /* GATT database definitions */
#include "JJ_test_service.h"

#define PIO_BUTTON      1          /* PIO connected to the button on CSR10xx */
#define PIO_LED_RED     9          /* PIO connected to the RED on CSR10xx */
#define PIO_LED_GREEN   10           /* PIO connected to the GREEN on CSR10xx */
#define PIO_LED_BLUE    11          /* PIO connected to the BLUE on CSR10xx */

#define PIO_DIR_OUTPUT  TRUE        /* PIO direction configured as output */
#define PIO_DIR_INPUT   FALSE       /* PIO direction configured as input */

gatt_client_config jj_client_cfg;
typedef struct _LIGHT_CTL
{
    uint8   red;
    uint8   green;
    uint8   blue;
} LIGHT_CTL;

LIGHT_CTL jj_light_ctrl;

extern void JJ_Test_InitChipReset(void)
{
    PioSetMode(PIO_LED_RED, pio_mode_pwm3);
    PioSetMode(PIO_LED_GREEN, pio_mode_pwm1);
    PioSetMode(PIO_LED_BLUE, pio_mode_pwm2);
    PioSetDir(PIO_LED_RED, PIO_DIR_OUTPUT);
    PioSetDir(PIO_LED_GREEN, PIO_DIR_OUTPUT);
    PioSetDir(PIO_LED_BLUE, PIO_DIR_OUTPUT);
    PioEnablePWM(1, TRUE);
    PioEnablePWM(2, TRUE);
    PioEnablePWM(3, TRUE);
    PioConfigPWM(3, pio_pwm_mode_inverted_push_pull,
                 0, 255, 0,
                 0, 255, 255, 0);
    PioConfigPWM(1, pio_pwm_mode_inverted_push_pull,
                 0, 255, 0,
                 0, 255, 255, 0);
    PioConfigPWM(2, pio_pwm_mode_inverted_push_pull,
                 0, 255, 255,
                 0, 255, 255, 0);
}

extern void JJ_Test_DataInit(void)
{
    jj_client_cfg = gatt_client_config_none;
}

extern void JJ_Test_ReadDataFromNVM(uint16 *nvm_offset)
{
}

extern bool JJ_Test_CheckHandleRange(uint16 handle)
{
    return ((handle >= HANDLE_JJ_test_service) &&
            (handle <= HANDLE_JJ_test_service_END))
            ? TRUE : FALSE;
}

extern void JJ_Test_HandleAccessRead(GATT_ACCESS_IND_T *p_ind)
{
    sys_status rc = sys_status_success; /* Function status */
    uint16 result;
    switch (p_ind->handle)
    {
        case    HANDLE_RANDOM_NUMBER:
            result = Random16();
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 1, (uint8*)&result);
            break;
        case    HANDLE_RANDOM_NUMBER_C_CFG:
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 2, (uint8*)&jj_client_cfg);
            break;
        case    HANDLE_LIGHT_CTL:
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 3, (uint8*)&jj_light_ctrl);
            break;
        default:
            rc = gatt_status_read_not_permitted;
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
    }
}

extern void JJ_Test_HandleAccessWrite(GATT_ACCESS_IND_T *p_ind)
{
    sys_status rc = sys_status_success; /* Function status */
    switch (p_ind->handle)
    {
        case    HANDLE_RANDOM_NUMBER_C_CFG:
            jj_client_cfg = BufReadUint16(&p_ind->value);
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
            break;
        case    HANDLE_LIGHT_CTL:
            jj_light_ctrl = *((LIGHT_CTL*)p_ind->value);
            PioConfigPWM(3, pio_pwm_mode_inverted_push_pull,
                         jj_light_ctrl.red, 255-jj_light_ctrl.red, 0,
                         jj_light_ctrl.red, 255-jj_light_ctrl.red, 255, 0);
            PioConfigPWM(1, pio_pwm_mode_inverted_push_pull,
                         jj_light_ctrl.green, 255-jj_light_ctrl.green, 0,
                         jj_light_ctrl.green, 255-jj_light_ctrl.green, 255, 0);
            PioConfigPWM(2, pio_pwm_mode_inverted_push_pull,
                         jj_light_ctrl.blue, 255-jj_light_ctrl.blue, 0,
                         jj_light_ctrl.blue, 255-jj_light_ctrl.blue, 255, 0);
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
        default:
            rc = gatt_status_write_not_permitted;
            GattAccessRsp(p_ind->cid, p_ind->handle, rc, 0, NULL);
    }
}

void notificationTimerHandler(timer_id tid)
{
    uint16   temp;
    temp = Random16();
    if (jj_client_cfg == gatt_client_config_notification)
    {
        GattCharValueNotification (GetConnectionID(), HANDLE_RANDOM_NUMBER, 1, (uint8*)&temp);
    }
    TimerCreate(2*SECOND, TRUE, notificationTimerHandler);
}

As CSR is not providing a mobile phone app to be used, I'll use a generic BLE app from Nordic, another Bluetooth chip manufacture. That seems a bit weird but it works.

A video:

Monday 14 August 2017

Importing 3D model for KiCAD footprints

I've been using KiCAD for a while and had created a few PCBs with it. I have to say it's very impressive and worth for every one to use, even for professional purpose. It's open source and free, good documented, and has no limitation on board size or connections etc.

One of the best things I love of KiCAD is the 3D viewer, letting you have a feel of what the PCB will look like before you send it out to the factory.
Although there are already many components have 3D models in the library, there will always the need to add you own footprints and needs to build 3D models for them. KiCAD itself has not provided the 3D model importing function yet, so you have to do it with the help of some third party software.

After some research on the internet, I have figured out how to do it and will list it step by step here, hope it will help somebody who need this.

Step 1 : You need to install a software named FreeCAD, it's an open source software too. KiCAD has support for Windows and Linux both, so does the FreeCAD, but I'm using the Ubuntu version of KiCAD, so the following work were all done in Ubuntu, should be the same in Windows. I have to admit that I had never used a modern 3D software before, the only experience I had used a CAD system was about 15 years ago using a version of AutoCAD with no 3D capability. But you don't need to know how to use the FreeCAD to export 3D models to KiCAD.

Step 2 : Install a plugin for FreeCAD named KiCAD STEPUP . You can find it on Sourceforge.

Step 3 : Find the 3D model for your component. Usually you will get it from the manufacturer's website. For example, here we'll build the 3D model for an inductor from Wurth Elektronik, order number 744787039.
Normally the 3D model will be in STEP format, a file named xxxxx.stp. To my understanding, any format that FreeCAD can accept will work.

Step 4 : Find out which footprint in the KiCAD library will suite the new component. To our example, I choose the "Inductor_Taiyo-Yuden_NR-60xx_HandSoldering"

If you use the 3D viewer to see it you can only see the pads on the PCB like above.

Step 5 : Run the KiCAD STEPUP tool. Don't open the FreeCAD directly, run the plugin and it will open the FreeCAD automatically. Switch to the directory in which KiCAD STEPUP Tool was installed and use the command :
sh launch-kicad_StepUp-Tools.sh
The FreeCAD will open with a special window:

Step 6 : Use the "Load kicad footprint kicad_mod" button to load the footprint.


Step 7 : Use the "Import STEP 3D model" button to import the 3D model.
This component happens to be at the right place. Sometimes the 3D model is not aligned with the PCB pads, in this case, use these buttons above the 'load' to adjust its position.

Step 8 : "Export to kicad". Select the 3D model and click this button, it will save a .wrl file.

Step 9 : In KiCAD, open the footprint editor, in the "properties->3D settings", change the 3D shape file (.wrl) with the one we just created. Done!

Saturday 29 July 2017

Wirtting code in C++ for small MCUs

When you think about C++ what's in your mind? I thought about those fancy complicated powerful GUI programs on PC like you until one day when I was looking at the IAR MSP430 IDE and realized it has been suported C++ for a long time.

If you look around you'll find that many compilers for MCU has the ability to support C++, for example the IAR, Keil, and the GCC. Wild ranges of MCUs can be developed in C++, like TI's MSP430, Atmel's AVR, ARM series from any manufacture, and even the MCS-51 if you choose the right compiler.

First of all, C++ doesn't mean big code size. Programs with GUI on PC becomes large because they use a lot of libraries, C++ itself is as efficient as C, if not better than. I had done a commercial project with MSP430F2312, which has only 8KB of FLASH. I wrote the entire code in C++ and it turns out to be one of the most efficient code I have ever done. Until now I still couldn't believe I had packed so many functionalities in such a small chip.

C++ has 3 major benefits over C, inheritance, polymorphism, and templates. Unfortunately, many C++ compilers for MCUs do not support templates, but polymorphism alone is a thing good enough for you to consider using it. Imagining you wrote a software I2C interface, and you can easily change the configuration to assign which I/O port to assign it, or even changing the mapping I/O on the fly!

Here I'll give an easy example with STM32F103 using Keil's SDK. The code is very simple, it just blink a LED on an I/O pin. Not using any RTOS, not using any timer, interrupt, or any library.

First there is the plain C version:
#include <stm32f10x.h>

#define LED1        0x2000      // PC13

void RCC_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
}

void blink()
{
    unsigned long i;
    while(1)
    {
        GPIOC->ODR ^= LED1;
        for (i=0; i<0x400000; i++);
    }
}    

int main(void)
{
    RCC_Init();
    GPIOC->CRH = 0x44244444;
    blink();
    return 0;
}

The C++ version is a little bit longer than the C code, because there need to be the definitions of the class:
file gpio.h:
#ifndef GPIO_H
#define GPIO_H

class GPIO
{
    public:
    enum Mode
    {
        FLOAT_INPUT = 0x04,
        PUSH_PULL_50M = 0x03,
        PUSH_PULL_2M = 0x02,
        PUSH_PULL_10M = 0x01,
        ANALOG_INPUT = 0x00,
        ALT_PUSH_PULL_50M = 0x0B,
        ALT_PUSH_PULL_2M = 0x0A,
        ALT_PUSH_PULL_10M = 0x09,
        PULL_UP_DOWN_INPUT = 0x08
    };
    GPIO() {}
    virtual ~GPIO() {}
    virtual void Set1() = 0;
    virtual void Set0() = 0;
    virtual void Toggle() = 0;
};

#endif

File main.cpp:
#include "stm32f10x.h"
#include "gpio.h"

class PortCPin : public GPIO
{
    private:
    uint16_t pin;
    public:
    PortCPin( unsigned char pin_number, GPIO::Mode mode = FLOAT_INPUT)
    {
        pin = (uint16_t)1<<pin_number;
        if (pin_number >= 8)
        {
            pin_number -= 8;
            GPIOC->CRH = (GPIOC->CRH & (~((uint32_t)0x0F << (pin_number*4)))) | ((uint32_t)mode << (pin_number*4));
        }
        else
        {
            GPIOC->CRL = (GPIOC->CRL & (~((uint32_t)0x0F << (pin_number*4)))) | ((uint32_t)mode << (pin_number*4));
        }
    }
    ~PortCPin()
    {
    }
    void Set1()
    {
        GPIOC->BSRR = pin;
    }
    void Set0()
    {
        GPIOC->BRR = pin;
    }
    void Toggle()
    {
        GPIOC->ODR ^= pin;
    }
};

void RCC_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
}

void blink(GPIO& pin)
{
    while(1)
    {
        pin.Toggle();
        for (unsigned long i=0; i<0x400000; ++i);
    }
}

int main()
{
    RCC_Init();
    PortCPin    PC13(13, GPIO::PUSH_PULL_2M);
    blink(PC13);
    return 0;
}

The idea behind the code is, the GPIO is a base class of all the independent I/O pins, it provides a common interface for all the I/O operations (here I only included Set1(), Set0(), Toggle() 3 operations, there should be more like read() change_mode() etc). A specific I/O pin should be an instance of a derived class of GPIO. All the functions in the code should use these common interface to operate I/Os, then by power of polymorphism, once these functions are written, they can be used on any I/O pins.

One thing should be of caution is, the definition of PC13 must be behind the RCC_Init() code, it can not be declared as a global, because then the constructor will be executed before the main() then before the RCC is initialized.

If you are curious about the code efficiency, the plain C code is 744 bytes long (mainly because of the system initialization code not listed here), while the C++ version is 788 bytes.

Saturday 22 July 2017

Connect ESP8266 in Ubuntu

I received my ESP-launcher board a few days ago, 2 ESP8266 module and a ESP32 dev kit have also been ordered but I decide to leave them for later use, would stick to the launcher board at this stage.

The SDK for ESP8266 is based on linux, the user manual suggest you to install a virtual machine in Windows and do your job under windows. I don't know why they do this, probably because their download tool is for Windows only ? For me that doesn't make sense. I won't waste my hard drive space to install a virtual machine while I already have a Ubuntu installed as a dual boot.

The simplest way to play with a ESP8266 is to use the AT command, you just need a terminal and a USB port. There is already a USB-UART chip FT-232 on the ESP-launcher board, and the good news is Ubuntu has built-in driver for it.


You don't need to turn on the launcher to check the FT-232's status. Connect the USB cable and go to the terminal in Ubuntu, use this command to check if the USB-UART chip has been recognized:
lsusb
See the second one from the last : Future Technology Devives International, Ltd FT232 USB-Serial (UART) IC

When it's correctly driven, you will get a new TTY device and that is the AT terminal to use. Use "ls /dev" command to check the devices and search for "ttyUSBn", on my computer, it's ttyUSB0.





Unfortunately, Ubuntu doesn't come with a proper terminal software to communicate via these serial ports, you have to download the "minicom" software. It's easy just use "sudo apt-get install minicom" form the terminal.

run minicom in the terminal, you have to use "sudo minicom" otherwise it will not run. For the first time, use "sudo minicom -s" to enter setup and set the serial port to the USB, be sure to set it to 115200, 8N1, like this:





When everything is set, turn the ESP-Launcher on, ta-da! you see the "ready!" from the ESP8266!

Now let's try some AT commands: AT+GMR displays the version information the ESP8266 chip, and AT+CWLAP displays the wifi hotspots arround:

Remember, you have to use Ctrl-m + Ctrl-j at the end of AT command, or use
ENTRE+Ctrl-j , seems the ESP8266 needs a Windows like end-of-line mark.

Connect to the WiFi :
That's it! You can't do much with just a terminal and ESP8266, to have more fun you have to do some programming, either program the ESP8266 itself or connect an external MCU, I'll try it later and write a post then.

Thursday 13 July 2017

USB HID demo with STM32 and emWin and FreeRTOS





This is a demonstration of the HID communication. The computer program reads the LED status via the USB and shows it on screen with 3 big indicators. In the mean while the scroll bar on the computer is used to control the flashing speed of the LED on board.



The board is using a STM32F103 MCU which has a USB 2.0 device full speed port. I didn't use the ST peripheral library or the ST's USB stack, all the USB code was built from scratch and is completely interrupt driven. So the code is compact and efficient, the USB core code is less than 400 lines.



FreeRTOS is used as the operating system to provide support of the GUI and multi-tasking. 3 software timers were used to control the LED flashing speed, the GUI refreshing, and the USB idle time. The benefit of using the software timer of the RTOS is there is no additional hardware and software cost to the system.



The whole tool chain I used is free except the Keil compiler. It is possible to use a free GNU compiler, anyway I bought the Keil several years ago and I can't see any reason not using it by now. The emWin is free to use if you are using the STM32 chips thanks for a deal made between ST and Segger. FreeRTOS is free of charge, even for commercial purpose. The compiler for the Windows program is Embarcadero's C++ builder, which is free to build Win32 applications now, for both personal and commercial use.

I would like to provide the source codes for this demo, but haven't found a way to upload files in Blogger, which is this site based on, I'll update this blog when the download is available. Before that, maybe you can contact me to send you an email.

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.