2

I have a STM32F103C8 MCU, and I want to control GPIO registers without Cube MX. The MCU has an embedded LED and I want control it. I'm currently using CubeMX and IAR Software, and I make the pin an output (in CubeMX) with this code:

HAL_GPIO_TogglePin(Ld2_GPIO_Port,Ld2_Pin); 
HAL_Delay(1000); 

This works, but I want to do it without Cube and HAL library; I want to edit the register files directly.

Das_Geek
  • 2,775
  • 7
  • 20
  • 26
GiGeNCo
  • 31
  • 1
  • 3

2 Answers2

7

Using GPIO using registers is very easy. You fo not have to write your own startup (as ion the @old_timer answer). Only 2 steps are needed

you will need the STM provided CMSIS headers with datatypes declarations and human readable #defines and the reference manual

  1. Enable GPIO port clock. ecample: RCC -> APB2ENR |= RCC_APB2ENR_IOPAEN;
  2. Configure the pins using CRL/CRH GPIO registers
#define GPIO_OUTPUT_2MHz (0b10)
#define GPIO_OUTPUT_PUSH_PULL (0 << 2)
  GPIOA -> CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
  GPIOA -> CRL |= GPIO_OUTPUT_2MHz | GPIO_OUTPUT_PUSH_PULL; 
  1. Manipulate the output
  /* to toggle */
  GPIOA -> ODR ^= (1 << pinNummer);
  /* to set */
  GPIOA -> BSRR = (1 << pinNummer);
  /* to reset */
  GPIOA -> BRR = (1 << pinNummer);
  //or
  GPIOA -> BSRR = (1 << (pinNummer + 16));
0___________
  • 60,014
  • 4
  • 34
  • 74
4

It is very good to know how to do bare metal without the canned libraries, and or to be able to read through those libraries and understand what you are getting yourself into by using them.

This blinks port C pin 13 that is where you generally find the user led on the stm32 blue pill boards. You can figure it out from here and the documentation for the STM32F103C8.

flash.s

.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop

.thumb_func
reset:
    bl notmain
    b loop
.thumb_func
loop:   b .

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

so.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );

#define GPIOCBASE 0x40011000
#define RCCBASE 0x40021000

#define STK_CSR 0xE000E010
#define STK_RVR 0xE000E014
#define STK_CVR 0xE000E018
#define STK_MASK 0x00FFFFFF

static int delay ( unsigned int n )
{
    unsigned int ra;

    while(n--)
    {
        while(1)
        {
            ra=GET32(STK_CSR);
            if(ra&(1<<16)) break;
        }
    }
    return(0);
}

int notmain ( void )
{
    unsigned int ra;
    unsigned int rx;

    ra=GET32(RCCBASE+0x18);
    ra|=1<<4; //enable port c
    PUT32(RCCBASE+0x18,ra);
    //config
    ra=GET32(GPIOCBASE+0x04);
    ra&=~(3<<20);   //PC13
    ra|=1<<20;      //PC13
    ra&=~(3<<22);   //PC13
    ra|=0<<22;      //PC13
    PUT32(GPIOCBASE+0x04,ra);

    PUT32(STK_CSR,4);
    PUT32(STK_RVR,1000000-1);
    PUT32(STK_CVR,0x00000000);
    PUT32(STK_CSR,5);

    for(rx=0;;rx++)
    {
        PUT32(GPIOCBASE+0x10,1<<(13+0));
        delay(50);
        PUT32(GPIOCBASE+0x10,1<<(13+16));
        delay(50);
    }
    return(0);
}

flash.ld

MEMORY
{
    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}

build

arm-none-eabi-as --warn --fatal-warnings flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mthumb -c so.c -o so.o
arm-none-eabi-ld -o so.elf -T flash.ld flash.o so.o
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf so.bin -O binary

PUT32/GET32 is IMO a highly recommended style of abstraction, decades of experience and it has many benefits over the volatile pointer or worse the misuse of unions thing that is the current FAD. Not meant to be a library but to show code that does not require any libraries, only the files provided are required.

Most mcus you need to enable clocks to the peripheral before you can talk to it. You can see the read-modify-write of an RCC register.

Most MCUs the GPIO pins reset to inputs so you need to set one to an output to drive/blink an led. Even within the STM32 world but certainly across brands/families the GPIO (and other) peripherals are not expected to be identical nor even compatible so you have to refer to the documentation for that part and it will show how to make a pin an output. very good idea to read-modify-write instead of just write, but since you are in complete control over the chip you can just write if you wish, try that later.

This chip has a nice register that allows us to change the output state of one or more but not necessarily all GPIO outputs in a single write, no read-modify-write required. So I can set or clear pin 13 of GPIOC without affecting the state of the other GPIOC pins.

Some cortex-ms have a systick timer, for example not all cortex-m3s have to have one it is up to the chip folks usually and some cores may not have the option. This chip does so you can use it. In this example the timer is set to roll over every 1 million clocks, the delay function waits for N number of rollovers before returning. so 50,000,000 clocks between led state changes. since this code runs right from reset without messing with the clocking or other systems, the internal HSI 8MHz clock is used 50/8 = 6.25 seconds between led state changes. systick is very easy to use, but remember it is a 24 bit counter not 32 so if you want to do now vs then you must mask it.

I don't remember if it is an up counter

elapsed = (now - then) & 0x00FFFFFF;

or down

elapsed = (then - now) & 0x00FFFFFF;

(now = GET32(systick count register address))

The systick timer is in the arm documentation not the chip documentation necessarily although sometimes ST produces their own version, you want the arm one for sure and maybe then the st one. infocenter.arm.com (you have to give up an email address or you can Google sometimes you get lucky, someone will illegally post them somewhere) this chip will tell you it uses a cortex-m3 so find the technical reference manual for the cortex-m3 in that you will find it is based on architecture armv7-m so under architecture find the armv7-m documentation, between these you see how the vector table works, the systick timer and its addresses, etc.

Examine vector table

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000041    stmdaeq r0, {r0, r6}
 8000008:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800000c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000010:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000014:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000018:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800001c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000020:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000024:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000028:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800002c:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000030:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000034:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 8000038:   08000047    stmdaeq r0, {r0, r1, r2, r6}
 800003c:   08000047    stmdaeq r0, {r0, r1, r2, r6}

08000040 <reset>:
 8000040:   f000 f806   bl  8000050 <notmain>
 8000044:   e7ff        b.n 8000046 <loop>

08000046 <loop>:
 8000046:   e7fe        b.n 8000046 <loop>

The entry point code with our vector table which starts off with the value we would like to put in the stack pointer on reset should be the first thing, then vector tables which are the address of the handler ORRed with 1 (not as easy to find in the docs sometimes). the disassembly of these addresses is because I used the disassembler to view them those are not actual instructions in the vector table it is a table of vectors. the tool is just doing its best to disassemble everything, if you look at the rest of the output it also disassembles the ascii tables and other things which are also not code.

.data is not supported in this example a bunch more work would be required.

I recommend if/when you get yours working you then examine the HAL library sources to see that when you dig through layers of sometimes bloated or scary code, you will end up with the same core registers, they may choose to always configure all the gpio registers for example, speed and pull up/down, turn off the alternate function, etc. Or not. the above knows it is coming out of reset and the state of the system so doesn't go to those lengths for some peripherals you can pop the reset for that peripheral and put it in a known state rather than try to make a library that anticipates it being left in any condition and trying to configure from that state. YMMV.

It is good professionally to know how to work at this level as well as how to use libraries. An MCU chip vendor will often have two libraries, certainly for older parts like these, the current library product and the legacy library product, when a new library comes out to keep it fresh and competitive (looking) the oldest one will drop off from support and you sometimes have current and prior. depends on the vendor, depends on the part, depends on how they manage their software products (same goes for their IDEs and other tools).

Most of the stm32 parts esp a blue pill and other boards you can get do not require the fancy IDEs to program but external hardware is sometimes required unless you get a NUCLEO or Discovery board then you have at least enough to program the part with free software not attached to ST. with a nucleo it is mbed style where you simply copy the .bin file to the virtual usb drive and the board takes care of programming the development MCU.

halfer
  • 19,824
  • 17
  • 99
  • 186
old_timer
  • 69,149
  • 8
  • 89
  • 168
  • This answer goes too far. OP did not ask - how to create my own startup and libc – 0___________ Jan 01 '20 at 11:59
  • The op can ignore that part and look at the C code. If you want to replace the delay function then you want to control the thing from boot (control clock rate), cant start in the middle and/or need to know what the setup was up front to know how to undo it or use it to both setup the gpio and do the time measurement. might as well provide a complete few dozen lines of code sample that does 100%. 100% of the code is provided and any version of gcc/binutils from a decade and a half ago or so till now should build it. Navigating a hybrid is too hard to show here and not as simple. – old_timer Jan 01 '20 at 12:30
  • The OP did ask how to do it without the library, the startup is generally part of the environment including library from the chip vendor, its a turn key package. Being free from the library means being free from the package, you will find implementations where they are intimately linked and non-separable. – old_timer Jan 01 '20 at 12:35
  • 1
    no. OP asked how to do it without the STM HAL Library. STM provided startup code is not the part of it, and is not also the part of it. BTW your startup is not C compliant. – 0___________ Jan 01 '20 at 12:39
  • 1
    Secondly CMSIS headers are not part of the library and strongly suggest to use those definitions even if you want to be 100% bare metal. #defines are not libraries – 0___________ Jan 01 '20 at 12:41
  • If you look at both how arm is trying to use CMSIS and how badly they are implemented by the chip vendors, it is a strong suggestion to not use them and or learn how to not use them. ARM and cortex-ms were around a long while before this CMSIS mess came about. And the world survived. it is not arms business to try to drive how the chip vendors deal with their own portion of the chip the arm part is nearly invisible as so little of the code deals with it (as we see with chip vendors coming out with matching parts using risc-v) – old_timer Jan 01 '20 at 12:43
  • It is a valuable life lesson and good for a professional if that is what the OP is interested in being in this field to understand the problems with CMSIS in particular, as well as the problems with libraries in general as your boss wont care if you try to blame the library (that you should have inspected, blessed and as a result owned) when the product fails. It will be you that gets fired. Learn both, learn the pros and cons of both, but you have to start somewhere. – old_timer Jan 01 '20 at 12:45
  • Please feel free to vote to delete this answer if you are unhappy with it. – old_timer Jan 01 '20 at 12:47
  • First of all - Happy New Year :). Secondly CMSIS is split into two main things - definitions and some libraries which I do not use. The first part with human friendly definitions and typedefs is 100% OK\ – 0___________ Jan 01 '20 at 12:55
  • Happy New year as well. The OP didnt want libraries, the ST registers are part of the library. I could re-write this or use an ST timer rather than the ARM timer and get rid of any ARM registers (to avoid an ARM library). Please just provide your answer than just beat up on mine, I dont know why we have to go through this every time. Give the OP choices, kinda the point of this site, they get to choose between the answers as to one matches what they were after. – old_timer Jan 01 '20 at 15:45
  • I think this is a very good and complete answer which matches the question exactly. (And it is very usefull for me right now because that's exactly what I'm trying to do currently: programming an stm32f103 without cmsis "mess" and saving about 90% flash space, so thanks for that.) – Scheintod Mar 10 '20 at 12:07
  • Wow, thanks so much, old_timer, this was exactly what I wanted to know. – Kamilion Jan 26 '21 at 04:11