How to use Backup SRAM as EEPROM in STM32F4 - stm32

There are two ways of emulating EEPROM on the STM32F4:
On-chip 4 Kbytes backup SRAM
On-chip Flash, with specific software algorithm
The second option is described here: AN3969.
But google, unfortunately, hasn't been able to provide information on how to use the first option - using the 4Kb of backup SRAM as EEPROM?..
Can anyone help on the topic?

must do these:
Enable the PWR clock
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
Enable access to the backup domain
PWR_BackupAccessCmd(ENABLE);
Enable backup SRAM Clock
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE);
Enable the Backup SRAM low power Regulator to retain it's content in VBAT mode
PWR_BackupRegulatorCmd(ENABLE);
and you can write/read datas to sram (these codes from BKP_Domain codes in STM32F4xx_DSP_StdPeriph_Lib) (in my mcu stm32f417 BKPSRAM_BASE=0x40024000)
// Write to Backup SRAM with 32-Bit Data
for (i = 0x0; i < 0x100; i += 4) {
*(__IO uint32_t *) (BKPSRAM_BASE + i) = i;
}
// Check the written Data
for (i = 0x0; i < 0x100; i += 4) {
if ((*(__IO uint32_t *) (BKPSRAM_BASE + i)) != i){
errorindex++;
}
}
then if you want:
// Wait until the Backup SRAM low power Regulator is ready
while(PWR_GetFlagStatus(PWR_FLAG_BRR) == RESET)
{}
you can find these functions in STM32F4xx_DSP_StdPeriph_Lib.

after reading through the Reference Manual for stm32f4 and the stm32f405xx/stm32f407xx datasheet I agree that it isn't clear how to actually use the backup sram (or where it is located). Here is what I found. Both the RTC registers and backup SRAM contain some amount of storage that is maintained as long as you have battery power. The RTC contains 20 registers (80 bytes) and the backup sram (which is its own peripheral on AHB1 and located within the register address region) contains 0x1000 (4096 bytes). Neither are enabled by default.
in DM00037051 (stm32f405xx/stm32f407xx datasheet, p29):
The 4-Kbyte backup SRAM is an EEPROM-like memory area. It can be used to store
data which need to be retained in VBAT and standby mode. This memory area is
disabled by default to minimize power consumption (see Section 2.2.19:
Low-power modes). It can be enabled by software.
The backup registers are 32-bit registers used to store 80 bytes of user
application data when VDD power is not present. Backup registers are not reset
by a system, a power reset, or when the device wakes up from the Standby mode
(see Section 2.2.19: Low-power modes).
on page 71 of datasheet and p65 of the reference manual
AHB1 | 0x4002 4000 - 0x4002 4FFF | BKPSRAM
and page 73 of the datatasheet and p67 of the reference manual
APB1 | 0x4000 2800 - 0x4000 2BFF | RTC & BKP Registers
Page 118-119 of the reference manual contains information on enabling the backup SRAM and RTC registers.
NOTE: if you are already using the RTC in the backup domain and only need to store <= 80 bytes, then you are better off using the RTC backup registers because enabling the backup SRAM will basically double the current consumption (see table 25 in the stm32f405/7 datasheet).
here are my write and read functions for both backup SRAM and backup RTC registers
int8_t write_to_backup_sram( uint8_t *data, uint16_t bytes, uint16_t offset ) {
const uint16_t backup_size = 0x1000;
uint8_t* base_addr = (uint8_t *) BKPSRAM_BASE;
uint16_t i;
if( bytes + offset >= backup_size ) {
/* ERROR : the last byte is outside the backup SRAM region */
return -1;
}
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE);
/* disable backup domain write protection */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // set RCC->APB1ENR.pwren
PWR_BackupAccessCmd(ENABLE); // set PWR->CR.dbp = 1;
/** enable the backup regulator (used to maintain the backup SRAM content in
* standby and Vbat modes). NOTE : this bit is not reset when the device
* wakes up from standby, system reset or power reset. You can check that
* the backup regulator is ready on PWR->CSR.brr, see rm p144 */
PWR_BackupRegulatorCmd(ENABLE); // set PWR->CSR.bre = 1;
for( i = 0; i < bytes; i++ ) {
*(base_addr + offset + i) = *(data + i);
}
PWR_BackupAccessCmd(DISABLE); // reset PWR->CR.dbp = 0;
return 0;
}
int8_t read_from_backup_sram( uint8_t *data, uint16_t bytes, uint16_t offset ) {
const uint16_t backup_size = 0x1000;
uint8_t* base_addr = (uint8_t *) BKPSRAM_BASE;
uint16_t i;
if( bytes + offset >= backup_size ) {
/* ERROR : the last byte is outside the backup SRAM region */
return -1;
}
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE);
for( i = 0; i < bytes; i++ ) {
*(data + i) = *(base_addr + offset + i);
}
return 0;
}
int8_t write_to_backup_rtc( uint32_t *data, uint16_t bytes, uint16_t offset ) {
const uint16_t backup_size = 80;
volatile uint32_t* base_addr = &(RTC->BKP0R);
uint16_t i;
if( bytes + offset >= backup_size ) {
/* ERROR : the last byte is outside the backup SRAM region */
return -1;
} else if( offset % 4 || bytes % 4 ) {
/* ERROR: data start or num bytes are not word aligned */
return -2;
} else {
bytes >>= 2; /* divide by 4 because writing words */
}
/* disable backup domain write protection */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // set RCC->APB1ENR.pwren
PWR_BackupAccessCmd(ENABLE); // set PWR->CR.dbp = 1;
for( i = 0; i < bytes; i++ ) {
*(base_addr + offset + i) = *(data + i);
}
PWR_BackupAccessCmd(DISABLE); // reset PWR->CR.dbp = 0;
// consider also disabling the power peripherial?
return 0;
}
int8_t read_from_backup_rtc( uint32_t *data, uint16_t bytes, uint16_t offset ) {
const uint16_t backup_size = 80;
volatile uint32_t* base_addr = &(RTC->BKP0R);
uint16_t i;
if( bytes + offset >= backup_size ) {
/* ERROR : the last byte is outside the backup SRAM region */
return -1;
} else if( offset % 4 || bytes % 4 ) {
/* ERROR: data start or num bytes are not word aligned */
return -2;
} else {
bytes >>= 2; /* divide by 4 because writing words */
}
/* read should be 32 bit aligned */
for( i = 0; i < bytes; i++ ) {
*(data + i) = *(base_addr + offset + i);
}
return 0;
}

I had to jump from main program to bootloader on user request.
So I put some 'magic number' into BKPSRAM in main program, do CPU soft reset.
Bootloader always starts first.
It checks for 'magic number' if it is present, it executes, else starts main program
when using HAL this is how to jump to bootloader:
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__BKPSRAM_CLK_ENABLE();
*(__IO uint8_t *)0x40024000 = 42;//magic number
HAL_NVIC_SystemReset();
inside bootloader to read magic number it is enough to enable backup sram clock only (bootloader uses StdPeriphDriver).
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE);
extRequest = *(__IO uint8_t *)0x40024000;
if(extRequest == 42)
//run bootloader
cpu is stm32f407

Here is the example of HAL library to use backup SRAM.
#define WRITE_READ_ADDR 0x01 //offset value.you can change according to your application
uint32_t write_arr = 0xA5A5A5A6;
uint32_t read_arr;
int main()
{
enable_backup_sram();
writeBkpSram(write_arr);
while(1)
{
read_arr = readBkpSram();
}
}
void enable_backup_sram(void)
{
/*DBP : Enable access to Backup domain */
HAL_PWR_EnableBkUpAccess();
/*PWREN : Enable backup domain access */
__HAL_RCC_PWR_CLK_ENABLE();
/*BRE : Enable backup regulator
BRR : Wait for backup regulator to stabilize */
HAL_PWREx_EnableBkUpReg();
/*DBP : Disable access to Backup domain */
HAL_PWR_DisableBkUpAccess();
}
void writeBkpSram(uint32_t l_data)
{
/* Enable clock to BKPSRAM */
__HAL_RCC_BKPSRAM_CLK_ENABLE();
/* Pointer write on specific location of backup SRAM */
(uint32_t *) (BKPSRAM_BASE + WRITE_READ_ADDR) = l_data;
/* Disable clock to BKPSRAM */
__HAL_RCC_BKPSRAM_CLK_DISABLE();
}
uint32_t readBkpSram(void)
{
uint32_t i_retval;
/* Enable clock to BKPSRAM */
__HAL_RCC_BKPSRAM_CLK_ENABLE();
/* Pointer write from specific location of backup SRAM */
i_retval = *(uint32_t*) (BKPSRAM_BASE + WRITE_READ_ADDR);
/* Disable clock to BKPSRAM */
__HAL_RCC_BKPSRAM_CLK_DISABLE();
return i_retval;
}

I'm currently using the an STM32F2xx microcontroller. According to the datasheet:
The 4-Kbyte backup SRAM is an EEPROM-like area.
To retain the content of the RTC backup registers … when VDD is turned off, VBAT pin can be connected to an optional standby voltage supplied by a battery or by another source.
A supercap, for example, would be required to maintain the contents of the backup registers while the microcontroller is powered off.
Also, according to the document:
After reset, the backup domain (… backup SRAM) is protected against possible unwanted write accesses. To enable access to the backup domain, proceed as follows …
It gives you instructions on how to gain access to the backup domain by directly writing to the certain peripheral register. If you have access to the STM32F4xx library, you can call something like this (note: I'm using the STM32F2xx library):
PWR_BackupAccessCmd(ENABLE);
Note: There's is more to it than simply calling the above function, such as enabling the backup SRAM interface clock. Consult the STM32F4 series documentation.
There is a lot of documentation embedded in the library source that is invaluable and if it's available should be read.
On the STM32F2 series microcontroller, SRAM is located at the following memory address range:
0x40024000 - 0x40024FFF
And can be written to somewhere at location, for example, as follows:
#define VAR_LOC ((volatile uint8_t *)(0x40024000))
volatile uint8_t *pVar = VAR_LOC;
*pVar = 5;

Useable example
In header:
//------------------------------------
typedef struct
{
uint32_t isDefault; //must by 0x12345678
uint32_t LastTestNumber;
uint32_t LastUserNumber;
uint32_t LastModeTest;
uint32_t calibv;
uint32_t calibc;
uint32_t WorkTime;
int32_t RTCCalib;
uint32_t LCDContrast;
} sBKPSRAM;
extern sBKPSRAM *BKPSRAM;// = (sSDRAM *)SDRAM_BANK_ADDR;
//------------------------------------
In code head
define as data:
sBKPSRAM *BKPSRAM = (sBKPSRAM *)BKPSRAM_BASE;
In Init:
void main(void)
{
(....)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_BKPSRAM, ENABLE);
PWR_BackupAccessCmd(ENABLE);
PWR_BackupRegulatorCmd(ENABLE);
ifDefault();
(....)
}
In procedure:
//-------------------------------------------------
void ifDefault(void)
{
if (BKPSRAM->LastModeTest!=0x12345678)
{
printf("BKPSRAM to default\r\n");
memset(BKPSRAM,0,sizeof(sBKPSRAM));
BKPSRAM->calibv =66920;
BKPSRAM->calibc =79230;
BKPSRAM->RTCCalib =1;
BKPSRAM->LCDContrast =2;
BKPSRAM->LastModeTest =0x12345678;
}
}
//-------------------------------------------------

HAL Configuration for STM32H7 to access backup SRAM:
#define BKP_RAM (*(__IO uint32_t *) (D3_BKPSRAM_BASE)) //Start address: 0x38800000
Main() {
__HAL_RCC_BKPRAM_CLK_ENABLE();
HAL_PWREx_EnableBkUpReg();
BKP_RAM = 0xA5AA5A55;
}
In addition to that, you need to add a below line in systemInit() to enable write-through access to Backup SRAM.
SCB->CACR |= 1<<2;

Related

Using STM32 FMC HAL driver with parallel DAC

Im trying to generate sinusoidal signal with STM32f767 and DAC8412. DAC have 12 bit data bus and 2 bit address to select one of the four analog outputs. I've configuried FMC in CubeIDE for a SRAM memory with 16 bit data and 2 bit addres. I was able to create buffer with 4096 integer values of sin(). Then i've tried to write them to addres 0x60000000, but it only writes 4 values. After that, program goes to HardFault_Handler().
#define SRAM_BANK_ADDR ((uint32_t)0x60000000)
#define RESOLUTION_T ((uint32_t)0x1000)
#define RESOLUTION_Y ((uint32_t)0x1000)
uint32_t aTxBuffer[RESOLUTION_T];
uint32_t address = SRAM_BANK_ADDR;
Thats how i try to send data to DAC:
for (uint32_t i = 0; i<RESOLUTION_T; i++ )
{
*(__IO uint32_t*) address = aTxBuffer[i];
}
Thats how i fill buffer:
static void Fill_Buffer(uint32_t *pBuffer, uint32_t res_T, uint32_t res_Y)
{
uint32_t tmpIndex = 0;
double sinVal;
/* Put in global buffer different values */
for (tmpIndex = 0; tmpIndex < res_T; tmpIndex++ )
{
sinVal = round((sin(M_TWOPI*tmpIndex/res_T)+1)*res_Y/2);
pBuffer[tmpIndex] = sinVal;
}
}

Reading value from HC SR04 in LM016L

I'm programming STM32F103C6, and I'm using Keil Microvision with Proteus Professional 8, I have problem with reading value from HC SR04 Ultrasonic sensor in LCD(LM016L), the code is below
#define TRIG_PIN GPIO_PIN_9
#define TRIG_PORT GPIOA
#define ECHO_PIN GPIO_PIN_8
#define ECHO_PORT GPIOA
uint32_t pMillis;
uint32_t Value1 = 0;
uint32_t Value2 = 0;
uint16_t Distance = 0; // cm
uint16_t temp = 50;
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim1);
HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); // pull the TRIG pin low
/* USER CODE END 2 */
lcd_init();
char buffer[24];
while (1)
{
HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET); // pull the TRIG pin HIGH
__HAL_TIM_SET_COUNTER(&htim1, 0);
while (__HAL_TIM_GET_COUNTER (&htim1) < 10); // wait for 10 us
HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); // pull the TRIG pin low
pMillis = HAL_GetTick(); // used this to avoid infinite while loop (for timeout)
// wait for the echo pin to go high
while (!(HAL_GPIO_ReadPin (ECHO_PORT, ECHO_PIN)) && pMillis + 10 > HAL_GetTick());
Value1 = __HAL_TIM_GET_COUNTER (&htim1);
pMillis = HAL_GetTick(); // used this to avoid infinite while loop (for timeout)
// wait for the echo pin to go low
while ((HAL_GPIO_ReadPin (ECHO_PORT, ECHO_PIN)) && pMillis + 50 > HAL_GetTick());
Value2 = __HAL_TIM_GET_COUNTER (&htim1);
Distance = (Value2-Value1)* 0.034/2;
sprintf(buffer, "%u\n", Distance);
lcd_puts(0,0, (int8_t*)(buffer));
HAL_Delay(50);
}
}
I think I got some value 10* -300, does anyone has idea how to read it or another code

DPDK implementation of MPSC ring buffer

While going through the implementation of the DPDK MPSC (multi-produce & single-consumer) Ring Buffer API, i found the code to move the head of the producer for inserting new elements in the Ring buffer. The function is as follows :
static __rte_always_inline unsigned int
__rte_ring_move_prod_head(struct rte_ring *r, unsigned int is_sp,
unsigned int n, enum rte_ring_queue_behavior behavior,
uint32_t *old_head, uint32_t *new_head,
uint32_t *free_entries)
{
const uint32_t capacity = r->capacity;
uint32_t cons_tail;
unsigned int max = n;
int success;
*old_head = __atomic_load_n(&r->prod.head, __ATOMIC_RELAXED);
do {
/* Reset n to the initial burst count */
n = max;
/* Ensure the head is read before tail */
__atomic_thread_fence(__ATOMIC_ACQUIRE);
/* load-acquire synchronize with store-release of ht->tail
* in update_tail.
*/
cons_tail = __atomic_load_n(&r->cons.tail,
__ATOMIC_ACQUIRE);
/* The subtraction is done between two unsigned 32bits value
* (the result is always modulo 32 bits even if we have
* *old_head > cons_tail). So 'free_entries' is always between 0
* and capacity (which is < size).
*/
*free_entries = (capacity + cons_tail - *old_head);
/* check that we have enough room in ring */
if (unlikely(n > *free_entries))
n = (behavior == RTE_RING_QUEUE_FIXED) ?
0 : *free_entries;
if (n == 0)
return 0;
*new_head = *old_head + n;
if (is_sp)
r->prod.head = *new_head, success = 1;
else
/* on failure, *old_head is updated */
success = __atomic_compare_exchange_n(&r->prod.head,
old_head, *new_head,
0, __ATOMIC_RELAXED,
__ATOMIC_RELAXED);
} while (unlikely(success == 0));
return n;
}
The load and compare exchange of the producer's head is done using __ATOMIC_RELAXED memory ordering. Isn't this a problem when multiple producers from different threads produce to the queue. Or am I missing something?
https://doc.dpdk.org/guides/prog_guide/ring_lib.html describes the basic mechanism that DPDK uses for implementing the Ring buffer.

MCP79411 RTC connection via i2c/TWI interface on Atmel SAMG55

I've made a project based on ATSAMG55J19 MCU, programmed with Atmel Studio and ASF 3
Now i'm trying to add an external RTC clock, because internal SAMg55 rtc does not have a backup battery.
The module will be used to read current time after power failure, then i'll use internal RTC, so i need only basic communication. No need to write specific data in EEPROM or setting alarms.
I have a MCP79411, connected via i2c, but there aren't any library suitable for this MCU that uses ASF TWI library.
There are many Arduino implementation, but they uses Wire.h library, and i can't port it.
I made an attempt porting this simple "driver": https://www.ccsinfo.com/forum/viewtopic.php?t=54105
Here is some code
static void i2c_start(void){
static twi_options_t ext3_twi_options;
flexcom_enable(FLEXCOM4);
flexcom_set_opmode(FLEXCOM4, FLEXCOM_TWI);
ext3_twi_options.master_clk = sysclk_get_cpu_hz();
ext3_twi_options.speed = 100000;
ext3_twi_options.smbus = 0;
twi_master_init(TWI4, &ext3_twi_options);
}
// Init Real Time Clock
void rtc_Init(void)
{
uint8_t seconds = 0;
i2c_start();
twi_write_byte(TWI4, ADDR_RTCC_WRITE); // WR to RTC
twi_write_byte(TWI4, ADDR_SEC); // REG 0
twi_write_byte(TWI4, ADDR_RTCC_READ); // RD from RTC
seconds = bcd2bin(i2c_read(0)); // Read current "seconds" in rtc
//i2c_stop();
//seconds &= 0x7F;
seconds |= 0x80; //set to 1 bit 7 of seconds(ST) enabling oscillator
delay_us(3);
twi_write_byte(TWI4, ADDR_RTCC_WRITE); // WR to RTC
twi_write_byte(TWI4, ADDR_SEC); // REG 0
twi_write_byte(TWI4, bin2bcd(seconds) | 0x80); // Start oscillator with current "seconds value
twi_write_byte(TWI4, ADDR_RTCC_WRITE); // WR to RTC
twi_write_byte(TWI4, 0x07); // Control Register
twi_write_byte(TWI4, 0x80); // Disable squarewave output pin
//i2c_stop();
}
Then i tried rtc_set_date_time(uint8_t day, uint8_t mth, uint8_t year, uint8_t dow, uint8_t hr, uint8_t min, uint8_t sec)
and
void rtc_get_time(uint8_t &hr, uint8_t &min, uint8_t &sec)
{
twi_write_byte(TWI4, ADDR_RTCC_WRITE);
twi_write_byte(TWI4, 0x00);
twi_write_byte(TWI4, ADDR_RTCC_READ);
sec = bcd2bin(twi_read_byte(TWI4) & 0x7f); //0x7f b01111111
min = bcd2bin(twi_read_byte(TWI4) & 0x7f); //0x7f
hr = bcd2bin(twi_read_byte(TWI4) & 0x3f); //0x3f b00111111
//i2c_stop();
}
But i always get "0" bytes.
i could not understand the correct way to open communication and read bytes from i2c.
The only reference i found is http://asf.atmel.com/docs/latest/sam.drivers.twi.twi_eeprom_example.samg53_xplained_pro/html/index.html but it seems to be a very different type of communication.
What is the correct way to send and receive that bytes via i2c?
I managed to get and set data. I post a draft of library:
#include "asf.h"
#include "conf_board_3in4out.h"
#include "external_rtc.h"
#include <time.h>
//#include <time_utils.h>
twi_packet_t packet_tx, packet_rx;
// helper functions to manipulate BCD and binary to integers
static int bcd2dec(char r_char)
{
MSN = (r_char & 0xF0)/16;
LSN = r_char & 0x0F;
return(10*MSN + LSN);
}
static char msn(char tim)
{
return (tim & 0xF0)/16;
}
static char lsn(char tim)
{
return (tim & 0x0F);
}
#define RTC_ADDR 0x6F // 7 bits
char config_t[10];
char config_2[8];
char tim_read[8];
#define ADDR_SEC 0x00 // address of SECONDS register
#define ADDR_MIN 0x01 // address of MINUTES register
#define ADDR_HOUR 0x02 // address of HOURS register
#define ADDR_DAY 0x03 // address of DAY OF WEEK register
#define ADDR_STAT 0x03 // address of STATUS register
#define ADDR_DATE 0x04 // address of DATE register
#define ADDR_MNTH 0x05 // address of MONTH register
#define ADDR_YEAR 0x06 // address of YEAR register
#define ADDR_CTRL 0x07 // address of CONTROL register
#define ADDR_CAL 0x08 // address of CALIB register
#define ADDR_ULID 0x09 // address of UNLOCK ID register
#define ADDR_SAVtoBAT_MIN 0x18 // address of T_SAVER MIN(VDD->BAT)
#define ADDR_SAVtoBAT_HR 0x19 // address of T_SAVER HR (VDD->BAT)
#define ADDR_SAVtoBAT_DAT 0x1a // address of T_SAVER DAT(VDD->BAT)
#define ADDR_SAVtoBAT_MTH 0x1b // address of T_SAVER MTH(VDD->BAT)
#define START_32KHZ 0x80 // start crystal: ST = b7 (ADDR_SEC)
#define OSCON 0x20 // state of the oscillator(running or not)
#define VBATEN 0x08 // enable battery for back-up
static uint8_t bin2bcd(uint8_t binary_value)
{
uint8_t temp;
uint8_t retval;
temp = binary_value;
retval = 0;
if(temp >= 10)
{
temp -= 10;
retval += 0x10;
}
else
{
retval += temp;
//break;
}
return(retval);
}
static uint8_t bcd2bin(uint8_t bcd_value)
{
uint8_t temp;
temp = bcd_value;
temp >>= 1;
temp &= 0x78;
return(temp + (temp >> 2) + (bcd_value & 0x0f));
}
static void setConfig(void){
config_2[0] = tim_read[0] | START_32KHZ; // bitwise OR sets Start osc bit = 1
config_2[1] = tim_read[1];
config_2[2] = tim_read[2];
//0x03h – Contains the BCD day. The range is 1-7.
//Bit 3 is the VBATEN bit. If this bit is set, the
//internal circuitry is connected to the VBAT pin
//when VCC fails. If this bit is ‘0’ then the VBAT pin is
//disconnected and the only current drain on the
//external battery is the VBAT pin leakage.
config_2[3] = tim_read[3] | VBATEN;
config_2[4] = tim_read[4];
config_2[5] = tim_read[5];
config_2[6] = tim_read[6];
config_2[7] = 0x00; // control b3 - extosc = 0
}
static void initialize(void){
uint8_t buf[7]; // Fill this with RTC clock data for all seven registers
// read stored time data
config_t[0] = 0x00; //reset pointer reg to '00'
// Set up config to read the time and set the control bits
//i2c.write(addr, config_t, 1); // write address 00
packet_tx.chip = RTC_ADDR;
packet_tx.addr[0] = 0; // RTCSEC
packet_tx.addr_length = 1;
packet_tx.buffer = config_t;
packet_tx.length = 1;
twi_master_write(TWI4, &packet_tx);
delay_ms(250);
//
//i2c.read(addr, tim_read, 7); //read time ss mm hh from r1, r2, r3
packet_rx.chip = RTC_ADDR;
packet_rx.addr[0] = 0; // RTCSEC
packet_rx.addr_length = 1;
packet_rx.buffer = tim_read;
packet_rx.length = sizeof(tim_read);
twi_master_read(TWI4, &packet_rx);
delay_ms(250);
setConfig(); //puts RTCC data into config array from tim_read array
// write the config data
//i2c.write(addr, config_t, 9); // write the config data back to the RTCC module
packet_tx.chip = RTC_ADDR;
packet_tx.addr[0] = 0; // RTCSEC
packet_tx.addr_length = 1;
packet_tx.buffer = config_2;
packet_tx.length = sizeof(config_2);
twi_master_write(TWI4, &packet_tx);
}
static void write_time(void){
// re-calculate mins
mins = 8; //ORE 10:08
mins = mins%60;
ch_mins = 16*(mins/10) + mins%10;
MSN = msn(ch_mins);
LSN = lsn(ch_mins);
tim_read[1] = ch_mins;
inc_mins = 0;
//write the data back to RTCC
setConfig();
//i2c.write(addr, config_t, 9);
/* Configure the data packet to be transmitted */
packet_tx.chip = RTC_ADDR;
packet_tx.addr[0] = 0; // RTCSEC
packet_tx.addr_length = 1;
packet_tx.buffer = config_2;
packet_tx.length = sizeof(config_2);
twi_master_write(TWI4, &packet_tx);
//Display and set hours
//hrs = bcd2dec(tim_read[2]);
// re-calculate hrs
hrs = 10; //ORE 10:08
hrs = hrs%24;
ch_hrs = 16*(hrs/10) + hrs%10;
MSN = msn(ch_hrs);
LSN = lsn(ch_hrs);
tim_read[2] = ch_hrs;
inc_hr = 0;
//write the data back to RTCC
setConfig();
/* Configure the data packet to be transmitted */
packet_tx.chip = RTC_ADDR;
packet_tx.addr[0] = 0; // RTCSEC
packet_tx.addr_length = 1;
packet_tx.buffer = config_2;
packet_tx.length = sizeof(config_2);
twi_master_write(TWI4, &packet_tx);
}
static void read_time(void){
//config_t[0] = 0x00; //reset pointer reg to '00'
//// First Get the time
////i2c.write(addr, config_t, 1); // write address 00
//packet_tx.chip = RTC_ADDR;
//packet_tx.addr[0] = 0; // RTCSEC
//packet_tx.addr_length = 1;
//packet_tx.buffer = config_t;
//packet_tx.length = 1;
//
//twi_master_write(TWI4, &packet_tx);
delay_ms(250);
uint8_t buf[7]; // Fill this with RTC clock data for all seven registers
/* Configure the data packet to be received */
packet_rx.chip = RTC_ADDR;
packet_rx.addr[0] = 0; // RTCSEC
packet_rx.addr_length = 1;
packet_rx.buffer = buf;
packet_rx.length = sizeof(buf);
twi_master_read(TWI4, &packet_rx);
for(uint8_t i = 0; i < sizeof(buf); i++){
tim_read[i] = buf[i];
}
}
void example_print_time(void){
//initialize();
delay_ms(1000);
//write_time(); //commented to see if time is permanent
delay_ms(1000);
read_time();
while(1){
printf("Reading time\n");
printf("%d:%d:%d\n", bcd2dec(tim_read[2]), bcd2dec(tim_read[1]), bcd2dec(tim_read[0] ^ START_32KHZ));
delay_ms(1000);
read_time();
}
}

Calibrating STM32 ADC (VREFINT)

I'm trying to read VDDA on an STM32F042 microcontroller. I'm getting unexpected results with VDD at 3.29V. I must be missing something fundamental.
output:
VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV
VREFINT=1885; VREFINT_CAL=1524; VDDA=2668 mV
VREFINT=1913; VREFINT_CAL=1524; VDDA=2628 mV
VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV
VREFINT=1917; VREFINT_CAL=1524; VDDA=2623 mV
adc_test.c:
#include <stdio.h>
#include "stm32f0xx.h"
#define VREFINT_CAL_ADDR 0x1FFFF7BA /* datasheet p. 19 */
#define VREFINT_CAL ((uint16_t*) VREFINT_CAL_ADDR)
extern void initialise_monitor_handles(void);
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; /* enable ADC peripheral clock */
RCC->CR2 |= RCC_CR2_HSI14ON; /* start ADC HSI */
while (!(RCC->CR2 & RCC_CR2_HSI14RDY)); /* wait for completion */
/* calibration */
ADC1->CR |= ADC_CR_ADCAL; /* start ADc CALibration */
while (ADC1->CR & ADC_CR_ADCAL); /* wait for completion */
ADC1->CR |= ADC_CR_ADEN; /* ADc ENable */
while (!(ADC1->ISR & ADC_ISR_ADRDY)); /* wait for completion */
ADC1->SMPR |= ADC_SMPR1_SMPR_0 | /* sampling mode: longest */
ADC_SMPR1_SMPR_1 |
ADC_SMPR1_SMPR_2;
/* VDD reference */
ADC->CCR |= ADC_CCR_VREFEN; /* VREF Enable */
ADC1->CHSELR = ADC_CHSELR_CHSEL17; /* CH17 = VREFINT */
initialise_monitor_handles(); /* enable semihosting */
while (1) {
ADC1->CR |= ADC_CR_ADSTART; /* start ADC conversion */
while (!(ADC1->ISR & ADC_ISR_EOC)); /* wait for completion */
uint32_t vdda = 3300UL * *VREFINT_CAL / ADC1->DR; /* ref. manual p. 252; constant and result in millivolts */
printf("VREFINT=%lu; VREFINT_CAL=%lu; VDDA=%lu mV\n",
(unsigned long)ADC1->DR,
(unsigned long)*VREFINT_CAL,
(unsigned long)vdda);
}
}
Screenshot from Datasheet:
Screenshot from Reference Manual
note this refers to .3V, but I believe this to be a typo, as the datasheet above and the longer formula below refer to 3.3V, and .3V is below minimum operating voltage for this part
I'm currently developing an ADC driver for STM32L4. During implementation I encounter almost the same problem. In my opinion the first formula
is not calculating the VDDA, but VREF+. It's the voltage against which the ADC is evaluating the ADC-IN channels.
Further the VREFINT_DATA is not measured VREF+ voltage, but an internal reference voltage which is controller dependent. In my case it defined in controller datasheet:
Here's a pic how I am using the posted formulas:
Some comments:
ln 102: calculating VREF+ not VDDA
ln 105-110: calculate all ranks/configured sequence
ln 108: calculate voltage measured by ADCpin_x
ln 109: multiply by gain to get real value
In my opinion by calculating the VREF+ for each conversion sequence, I'll get better results, because so some ripples on VREF+ are compensated.
as #Artur said Vref + is not Vdda, but usually (that's how I have it in my hardware design) Vref + is connected to Vdda (with the corresponding filters according to the datasheet), so calculating Vdda is the same as calculating Vref +.
I will show you how to calculate vdda, as I have it based on the STM32L431.
. You must first configure the ADC to measure VREFINT:
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_VREFINT;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
. Now I will show you the code where the equation is executed:
vdda = 3.0 * (VREFINT_CAL /average);
vch = VREF * (average / ADC_RESOLUTION);
log("vdd = %.5f - ", vdda);
log("vchn = %.5f", vch);
where:
#define ADC_RESOLUTION 4095.0 // adc resolution 12 bits
#define VREFINT_CAL 1655.00 // Raw data acquired at a temperature of 30 °C (± 5 °C), VDDA = VREF+ = 3.0 V (± 10 mV)
#define VREF 3.3 // voltage reference 3.3V
note:
'average' is an average of 256 samples taken by the adc (it's just a simple filter).
'log' is a function created by me similar to printf for the uart.
'VREFINT_CAL' varies according to the model.
result:
vdd = 3.28035 - vchn = 1.21343
as we see VREFINT matches the datasheet (1.212V typ.):
VREFINT
Actually it is calculating Vdda, since the Vref calculation is very simple, you have to read the corresponding channel of the ADC with a sample time longer than the one marked in the data sheet (usually 10 us). If Vdda is 2.0 V, a value of 4095 corresponds to 2.0 (or more) V absolute (related GND). In a linear way, the value of Vref will be much higher than if it is read with Vdda = 3.30 V. Therefore, the compensation of the values ​​read with 2.0 V is necessary to know the absolute values ​​of voltage that the ADC is measuring. If they are not compensated, they will be values ​​relative to the voltage level that Vdda has at that moment.
In addition, the power supply value is achieved, which will be useful in order not to go beyond the specifications of the microcontroller.
The answer (big thanks to #jasonharper) is a missing ground connection. Jason't comments on the OP are the best source of wisdom in this thread. I post a summary here so this question can have an accepted answer.
The board went through a number of revisions and in this iteration we forgot to connect the thermal pad, which on this part is the only ground connection. The chip was getting ground through ESD diodes on pins that were connected to ground. It's surprising to me that it worked at all. I was able to increase the current to the chip by configuring grounded GPIOs as outputs and setting them low.