Writing bare metal code for arduino

Hi all, I am trying to learn embedded system and I want to do it by learning how to write code bare metal. I am stuck at trying to make the external led blink. If i am using the internal LED which is at the LED_PIN 2 it is working but i am trying to make the external led also working. I have try a lot of way but cannot figure out. If i put in the pinMode(4, OUTPUT) everything work fine but since I want to do it bare metal I do not want to resolve the problem using arduino library. Do anyone know how to solve this? My board is WeMos D1 WiFi based ESP8266 Development Board Compatible With Arduino. Thanks

#define GPIO_OUT_REG       (*((volatile uint32_t *)0x60000300))
#define GPIO_OUT_W1TS_REG  (*((volatile uint32_t *)0x60000304))
#define GPIO_OUT_W1TC_REG  (*((volatile uint32_t *)0x60000308))
#define GPIO_ENABLE_REG    (*((volatile uint32_t *)0x6000030C))
#define GPIO_ENABLE_W1TS   (*((volatile uint32_t *)0x60000310))

#define LED_PIN 4
#define LED_BIT (1 << LED_PIN)

void setup() {
  GPIO_ENABLE_W1TS = LED_BIT;
}

void loop() {
  // Turn ON
  GPIO_OUT_W1TS_REG = LED_BIT;
  delay(1000);
  
  // Turn OFF
  GPIO_OUT_W1TC_REG = LED_BIT;
  delay(1000);
}
1 Like
  • What does this mean ?

  • Show us good images of your wiring.

1 Like

a.k.a. Low level, port manipulation.

1 Like

Then play with Arduino UNO R3 in which you can easily access the bits of the registers of MCU.

2 Likes

Please share that code.

Please share that code also.

Perhaps we can figure out the problem with the code you posted by comparing it to those two other codes.

Hi @GolamMostafa

I’d give the same advice!

1 Like

I believe if you look at what pinMode does in the ESP8266, you'll see that it's more than the single assignment your code uses.

And it's just a WAG, but I would not be surprised if the reason your code worked with the internal LED pin is that it's already been set up to be an OUTPUT once, whereas pin 4 hasn't been.

Ugh. You have probably picked a particularly difficult chip to do this with, since the "reference manual":

  • is mostly written in terms of the Espressif SDK.
  • is probably a translation from another language.
  • wasn't that good to start with.
  • the 8266 is a pretty weird chip.
  • The existing Arduino core for 8266 is pretty messy and hard to look at as an example.

In any case, I don't see an equivalent of the "PIN_FUNC_SELECT()" anywhere in your code. This is probably necessary in order to specify that the pin is to be used with GPIO (I'm not sure what the default it.)

I found this, that might be helpful: https://medium.com/@rudrasama/programming-esp8266-the-hard-way-and-loving-it-e3d5a031eadc

I think the wiring is not the issue cause it already worked when i write it with arduino function.

I have try to put the PIN_FUNCTION_SELECT() into the code but the led is still off, the issue most probaly be the pinMode() function and I am not sure how to replicate that bare metal.

I (on atmega) often dissassemble the compiled code to see, what it exactly does. Also you may look into Arduino sources to see, what exactly does pinMode() and copy that into your test code, then remove unneeded parts one by one (like decipher what pin number is translated into an then go next with this - for me it is some port name and pin number and pinMode is for me just DDRE |= BV(5) for output there - or cbi PORTE, 5 in assembly).

===>

DDRE |= _BV(5);
1 Like

Yes. Start with known-working code and pare it down little by little, testing after each change.

2 Likes

Can't comment further without seeing the new version of the code...

Unfortunately, as I said, the core code is rather ... difficult to read (and it doesn't disassemble very nicely, either.)

Filtering out the other cases, pinMode() looks like:

extern void __pinMode(uint8_t pin, uint8_t mode) {
  if(pin < 16){
     :
  if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN){
      GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
      GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
    }
     :
  }
}

GPF() is supposed to give you the Pin Function Register.
GPC() gives you the Pin Control register.

Why put any time/effort in that chip. It will be obsolete in 3 years.

My goodness, are we still writing about this?

As I said 2 days ago in post #7, just look in the core code to see what pinMode does. "When in doubt, go to the source."

#define LED_PIN 4 // D2 on D1 R2
#define LED_BIT (1 << LED_PIN)

void setup() {
   GPF(LED_PIN) = GPFFS(GPFFS_GPIO(LED_PIN));  //Set mode to GPIO
   GPC(LED_PIN) = (GPC(LED_PIN) & (0xF << GPCI));
   GPES = LED_BIT;
}

void loop() {
   // Turn ON
   GPOS = LED_BIT;
   delay(1000);

   // Turn OFF
   GPOC = LED_BIT;
   delay(1000);
}

And if you want to take it further down than that, look at the definitions of GPF, GPFFS, GPFFS_GPIO, GPC, GPOS and GPOC. And start printing out addresses where needed.

That is a mistake.

All it will do for you is to make your code hardware specific. It will not help you at all understand embedded systems in general. In other words your initial premiss is wrong.

1 Like

@phong260702
Just interested in what you mean by trying to learn.
Is this for personal use, professional use, curiosity?
I've a lot of admiration for anyone who can code at that level. It's more than my pay grade, or was. It's also too late in life.
I don't have the time to get involved with programming in that detail, but I do realise you will learn a lot about grass roots chip workings.
I suspect there's a lot more going on deeper down than port manipulation.
I don't think any of my projects would gain anything than what the Arduino IDE provides.
If the LED blinks, that's fine.
I know of one enthusiast who codes at "bare metal" level for hobby projects mainly for Atmel chips. Impressive, but not easy for others to understand.
However, if you are in an industry, churning out a lot of items that need programming, then pared back efficiency could be important.

1 Like