Since I’m coming a little more from the hardware-side of those projects, I prefer data sheets and low-level documentation. When tinkering with microcontrollers, I often had the feeling, that I spent too much time “reverse-engineering” overpowered abstraction layers, bringing together the code and what was really going on on the machine. I then decided to code the stuff as they call it “bare-metal”. The disadvantage is clear: the code may not be as re-usable as it could be. On the other side, I like to use the low-level user manuals directly for programming, because this kind of documentation is very reliable and comprehensive. Additionally, if coding some “non-standard” usage of peripherals, one would fall back to “register level” easily; so why not only code like this when tinkering near to the hardware?
On the coding level, when I want to use some peripheral, I define a pointer variable that points to a stuct. The struct represents the registers which are described in the Reference Manual of the µC. The addresses of the peripherals are defines in the header file. This way, I can just copy the information from the Reference Manual and the documentation of the manufacturer can directly be found in my code.
The following is an example of “bare-metal” definitions for the UART-Peripheral of my STM8L in a header file (stm8l15x_usart.h):
#ifndef __STM8L15x_USART_H
#define __STM8L15x_USART_H
#include "STM8L15x.h"
#define USART1_OFFS (USART_t*)0x005230
#define USART2_OFFS (USART_t*)0x0053E0
#define USART3_OFFS (USART_t*)0x0053F0
#define QQ_BAUD_16M_115k2 (uint16_t)0x008B
#define QQ_BAUD_16M_9k6 (uint16_t)0x0683
struct USART_s
{
volatile uint8_t SR;
volatile uint8_t DR;
volatile uint8_t BRR1;
volatile uint8_t BRR2;
volatile uint8_t CR1;
volatile uint8_t CR2;
volatile uint8_t CR3;
volatile uint8_t CR4;
volatile uint8_t CR5;
volatile uint8_t GTR;
volatile uint8_t PSCR;
};
typedef volatile struct USART_s USART_t;
void usart_init(uint32_t baudrate);
#endif//__STM8L15x_USART_
The corresponding register set is defined in the STM8L Reference Manual:
Depending on the need of initialization functions for the peripheral, I create a c-file (e.g. stm8l15x_usart.c) and link the resulting object file into the project, or not.
#include "STM8L15x_USART.h"
extern USART_t *USART;
extern GPIO_t *PORTG;
extern CLK_t *CLK;
void usart_init(uint32_t baudrate)
{
uint16_t baud_div;
CLK->PCKENR3 |= (1<<4/*USART3 Enable*/);
if(baudrate == 9600)
{
baud_div = QQ_BAUD_16M_9k6;
}
else
{
baud_div = QQ_BAUD_16M_115k2;
}
// Set Baud Rate
USART->BRR2 = (baud_div & 0x0F) | ((baud_div >> 8) & 0xF0);
// LSB
USART->BRR1 = (baud_div >> 4) & 0xFF; // MSB
// Init Control Regs
USART->CR1 = 0x00; // 8N1
USART->CR2 = 0x0C; // RX ON, TX ON
PORTG->DDR |= 2;
PORTG->CR1 |= 2;
}
