STM32L052 Analog-to-digital converter on registers - c

I went through the entire configuration of the Analog-to-digital converter [ADC]. When I worked on the registers, I made a mistake somewhere. Below are the configurations. The debugger via ST-Link after connecting 3.3 [V] to pins used in the project, during the measurement assigns them a value of 0x00 which indicates a failure. What am I doing wrong?
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
//ADC attach
RCC->IOPENR = RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN;
GPIOB->MODER = GPIO_MODER_MODE14_1 | GPIO_MODER_MODE15_1;
//ADC_IN8 & ADC_IN9
/* configure ADC */
ADC1->ISR &= ~ADC_ISR_EOCAL & ~ADC_ISR_AWD;
//calibration flag, WATCHDOG flag
ADC1->ISR |= ADC_ISR_ADRDY;
ADC1->CR &= ~ADC_CR_ADSTART;
//The software is allowed to write smp bit only when ADSTART=0
ADC1->SMPR |= ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 | ADC_SMPR_SMP_2;
//111: 160.5 ADC clock cycles
ADC1->CFGR1 &= ~ADC_CFGR1_SCANDIR;
//Scan Direction 0: Upward scan (from CHSEL0 to CHSEL18)
ADC1->CFGR1 |= ADC_CFGR1_AWDCH_3 | ADC_CFGR1_AWDEN | ADC_CFGR1_WAIT | ADC_CFGR1_CONT | ADC_CFGR1_AUTOFF;
//AWDCH[4:0]: Analog watchdog channel selection, Continuous Mode
ADC->CCR |= ADC_CCR_LFMEN | ADC_CCR_VREFEN;
//Low Frequency Mode, V REFINT enable
ADC1->CHSELR |= ADC_CHSELR_CHSEL8 | ADC_CHSELR_CHSEL9 | ADC_CHSELR_CHSEL17;
//Channel Select 8 & 9
ADC1->IER |= ADC_IER_EOCIE | ADC_IER_EOSEQIE | ADC_IER_OVRIE | ADC_IER_EOSMPIE;
NVIC_EnableIRQ(ADC1_COMP_IRQn);
NVIC_SetPriority(ADC1_COMP_IRQn,3);
while(1)
{
ADC1->CR |= ADC_CR_ADEN;// | ADC_CR_ADSTART;
//Start the ADC conversion
while ((ADC1->ISR & ADC_ISR_ADRDY));
//Wait for stand up
while ((ADC1->ISR & ADC_ISR_EOC));
//wait for conversion flag
ADC1->CR |= ADC_CR_ADCAL;
//End of the calibration
delay(100);
uint16_t napiecie = ADC1->DR;
uint8_t hi = ((napiecie >> 8) & 0xff);
uint8_t lo = ((napiecie >> 0) & 0xff);
//DISABLE ADC
if ((ADC1->CR & ADC_CR_ADSTART) != 0){
ADC1->CR |= ADC_CR_ADSTP;
while ((ADC1->CR & ADC_CR_ADSTP) != 0);
}
ADC1->CR |= ADC_CR_ADDIS; //ADC disable command
while ((ADC1->CR & ADC_CR_ADEN) != 0);
ADC1->CR &= ~ADC_CR_ADSTART & ~ADC_CR_ADEN;
//ADC1->CR &= ~ADC_ISR_ADRDY; //Clear the ADRDY bit in ADC_ISR register by programming this bit to 1 (optional).
}
void ADC1_COMP_IRQHandler(void)
/* Interupt ADC */
{
if(ADC1->ISR & ADC_ISR_EOC){
uint16_t napiecie = ADC1->DR;
uint8_t hi = ((napiecie >> 8) & 0xff);
uint8_t lo = ((napiecie >> 0) & 0xff);
}
}

Two times you used lines like
ADC1->ISR &= ~ADC_ISR_EOCAL | ~ADC_ISR_AWD;
which looks really strange to me, as if the defines are 1 bit wide, which they most likely are, their bitwise OR is 0xFFFFFFFF (all F, no 0) and you're not changing the ISR and the CR (later in the code) at all! You need to use a bitwise AND, no?
ADC1->ISR &= ~ADC_ISR_EOCAL & ~ADC_ISR_AWD;
...
ADC1->CR &= ~ADC_CR_ADSTART & ~ADC_CR_ADEN;
Else some working code is available at https://electronics.stackexchange.com/questions/287073/get-internal-temperature-or-voltage-stm32l0/287162
https://github.com/ChristopherJD/STM32L053R8/blob/master/Intern_Project/ADC.c
and https://www.digikey.com/eewiki/pages/viewpage.action?pageId=47644832

Related

STM32F031 - Code runs in line-by-line debugging but not otherwise. Some functions only run if defined as macros

I am writing code for the STM32F031K6T6 MCU using the Keil uVision. The IDE information is shown in the image below:
enter image description here
The C/C++ options for Target are configured as shown here:
enter image description here
I started a new project, selected the chip, and configured the run-time environment as below:
enter image description here
I initialized the clock and configured the Flash registers for the appropriate latency. I tested the frequency using MCO and it seems correct. I also initialized some GPIOs, UART, and the SysTick. The peripheral registered is modified as expected as seen on the System View for the respective peripheral in the debugging mode.
The problem is that some functions, such as functions for sending and receiving data via UART and some functions that use GPIO ports only work in debugging mode when I run the code line-by-line. If I click the run button the code gets stuck and the chip stops responding. I still see the VAL and CURRENT registers of the SysTick updating.
This is an example of a function that works:
void System_Clock_init(void){
FLASH->ACR &= ~FLASH_ACR_LATENCY;
FLASH->ACR |= FLASH_ACR_LATENCY | 0x01;
RCC->CR |= RCC_CR_HSION;
while((RCC->CR & RCC_CR_HSIRDY) == 0);
RCC->CR &= ~RCC_CR_HSITRIM;
RCC->CR |= 16UL << 3;
RCC->CR &= ~RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY) == RCC_CR_PLLRDY);
RCC->CFGR &= ~RCC_CFGR_PLLSRC;
RCC->CFGR |= 10UL << 18;
RCC->CFGR &= ~RCC_CFGR_HPRE;
RCC->CFGR &= ~RCC_CFGR_PPRE;
RCC->CR |= RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY) == 0);
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
This is an example of a function that doesn’t work:
void UV_LED_Driver(uint32_t d){
for(uint32_t i = 0; i<16; i++){
if(d&(((uint32_t)0x8000)>>i)){
SDI2_ON;
}
else {
SDI2_OFF;
}
CLK2
}
LATCH2
}
The macros used in the function above are defined as below:
// CLK2 -> PA5
// LE2 -> PA4
// SDI2 -> PA6
#define CLK2_OFF GPIOA->ODR |= (1UL << 5)
#define CLK2_ON GPIOA->ODR &= ~(1UL << 5)
#define LE2_OFF GPIOA->ODR |= (1UL << 4)
#define LE2_ON GPIOA->ODR &= ~(1UL << 4)
#define SDI2_ON GPIOA->ODR &= ~(1UL << 6)
#define SDI2_OFF GPIOA->ODR |= (1UL << 6)
#define CLK2 {CLK2_ON; us_Delay(1); CLK2_OFF;}
#define LATCH2 {LE2_ON; us_Delay(1); LE2_OFF;}
The GPIO pins used in the function above are initialized as follows:
// CLK2 -> PA5
// LE2 -> PA4
// SDI2 -> PA6
void UV_LED_Driver_Init(void){
RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
GPIOA->MODER &= ~((3UL << 8) | (3UL << 10) | (3UL << 12));
GPIOA->MODER |= ((1UL << 8) | (1UL << 10) | (1UL << 12));
GPIOA->OTYPER &= ~(0x70UL);
GPIOA->PUPDR &= ~((1UL << 8) | (1UL << 10) | (1UL << 12));
GPIOA->OSPEEDR &= ~((3UL << 8) | (3UL << 10) | (3UL << 12));
GPIOA->OSPEEDR |= ((1UL << 8) | (1UL << 10) | (1UL << 12));
GPIOA->ODR |= (0x70UL);
}
And the us_Delay() function is based on SysTick. These are defined as:
static uint32_t usDelay = 0;
void SysTick_init(uint32_t ticks){
SysTick->CTRL = 0;
SysTick->LOAD = ticks - 1;
NVIC_SetPriority(SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
void SysTick_Handler(void){
if(usDelay > 0){
usDelay--;
}
}
void us_Delay(uint32_t us){
usDelay = us;
while(usDelay != 0);
}
Now, this is the same UV_LED_Driver(uint32_t d) function defined as a macro (Runs as expected):
#define UV_LED_DRIVER(d) {for(int i = 0; i<16; i++){if(d&(0x000F>>i)){SDI2_ON;}else {SDI2_OFF;}CLK2}LATCH2}
This is the main():
#include <stm32f031x6.h>
#include "clock.h"
#include "LED_Driver.h"
#include "UART.h"
int main(void){
System_Clock_init();
Color_LED_Driver_Init();
UV_LED_Driver_Init();
Nucleo_Green_LED_Init();
UART_init();
SysTick_init(47);
//MCO_Init(); // Check PIN 18 (PA8) for the frequency of the MCO using an Oscilloscope
while(1){
UV_LED_DRIVER(~(0x0000)) // This runs well
//UV_LED_Driver((uint32_t)~(0x0000)); // If I run this line
//the debugger gets stuck here. It works if I run line-by-line
ms_Delay(100);
UV_LED_DRIVER(~(0xFFFF)) // This runs well
//UV_LED_Driver((uint32_t)~(0xFFFF)); // If I run this line
//the debugger gets stuck here. It works if I run line-by-line
ms_Delay(100);
}
}
Interestingly, if I define the functions as macros, they behave as desired. I finally tested the code on a STM32F429ZIT chip and it worked well, given the needed modifications in the initialization of the main clock and the GPIO.
Has anyone ever experienced anything similar or happens to know what could be causing this issue? I know that I could walk around this issue using CubeMX but I would like to find out what is causing this problem.
Thank you.
I asked the same question at the ST Community forum and the user waclawek.jan answered it. The problem is that I was calling the SysTick interrupt too often, not leaving any time for the main() to run. To fix the code, I just called the SysTick_init() function passing "479" as an argument instead of "47".
Thank you!

Unable to read multiple channels from single ADC with DMA [bare metal]

I'm trying to read two ADC channels sequentially from my STM32F407ZGT6 using DMA. I'm just trying to get the values from two potentiometers independently on each channel. Although the program doesn't crash, I does not update my variable's value (sensor_val).
I'm using DMA2_Stream0 Channel 0, since I'm using ADC1. For my ADC1, I'm using PB1 (channel 9) and PA1 (channel 1). I tried to follow this tutorial, except that I do not want to trigger my ADC from a timmer just yet, and I've been also checking the example on this question. My ADC callback also never gets called. As far as my understanding goes, the sequence of calls should be:
ADC1->SR EOC --> ADC->CR1 EOCIE --> DMA2_Stream0_IRQHandler() --> dma_ADC_callback()┐
⮤─────────────────────────────────────────────────────────┘
Maybe I do need to include a periodic call to read the ADC?
All the ADC/DMA functions are on their separate .c/.h files. I've already tried to declare sensor_val as a global variable or as an extern variable from the adc.c file, and both give the same result. Here is an approximate mwe of my code:
#include <stdint.h> //uint32_t
#include <stdio.h> //printf
#include "stm32f407xx.h"
#define set(val, pos) ((val) << (pos))
#define msk(size, pos) (((1UL << (size)) - 1UL) << (pos))
static uint32_t sensor_val[2];
static void dma_ADC_callback(void);
void gpio_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Port A
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Port B
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Port C
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //Port D
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; //Port F
RCC->AHB1ENR;
//PORT B
GPIOA->MODER |= set(m,(2)); //ADC || PA1 || ADC123_IN1
GPIOB->MODER |= set(m,(2)); //ADC || PB1 || ADC12_IN9
}
void adc_init(void)
{
/*Enable clock access to ADC*/
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
// RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
/* Config ADC parameters*/
/* Regular Sequence Register 3
* Since the sequence starts from
* the back, we need to set channels
* from SQ3[0] to SQ1[19]
*/
ADC1->SQR3 |= set(0b1001,0); //sets channel PB1 (ADC12_IN9) as 1st conversion
ADC1->SQR3 |= set(0b0001,5); //sets channel PA1 (ADC123_IN1) as 2nd conversion
ADC1->SQR1 |= set(0b0001,ADC_SQR1_L_Pos); //tells the channel sequence lenght = 2
/*If using more than one channel
* SCAN is required
*/
ADC1->CR1 |= set(1,ADC_CR1_SCAN_Pos);
/*Adjust ADC sample time
* The resulting frequency is
* APB2/#cycles:
* 000: 3 cycles
* 001: 15 cycles
* 010: 28 cycles
* 011: 56 cycles
* 100: 84 cycles
* 101: 112 cycles
* 110: 144 cycles
* 111: 480 cycles = 42MHz/480 = 87.5kHz
* */
ADC1->SMPR2 |= set(0b111,ADC_SMPR2_SMP0_Pos); //channel 0
ADC1->SMPR2 |= set(0b111,ADC_SMPR2_SMP9_Pos); //channel 9
/*Turn Interruption On*/
ADC1->CR1 |= set(1,ADC_CR1_EOCIE_Pos);
/*Enable ADC*/
ADC1->CR2 |= set(1,ADC_CR2_ADON_Pos);
}
void adc_start_conversion(void)
{
ADC1->CR2 |= set(1,ADC_CR2_EOCS_Pos); //enables multi-channel conversion
ADC1->CR2 |= set(1,ADC_CR2_CONT_Pos); //enables continuous conversion
ADC1->CR2 |= set(1,ADC_CR2_SWSTART_Pos); //starts conversion
}
/*ADC1 DMA2 => DMA2_Ch0_Stream0 and 4*/
/*ADC2 DMA2 => DMA2_Ch1_Stream2 and 3*/
/*ADC3 DMA2 => DMA2_Ch2_Stream0 and 1*/
void dma2_stream0_init(uint32_t memo, uint32_t periph, uint32_t len)
{
/*Enable clock acces to DMA*/
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
/*Diable DMA2 Stream 0*/
DMA2_Stream0->CR &= ~DMA_SxCR_EN;
/*Clear all interrupt flags of Stream 0*/
DMA1->LIFCR |= DMA_LIFCR_CFEIF0;
DMA1->LIFCR |= DMA_LIFCR_CDMEIF0;
DMA1->LIFCR |= DMA_LIFCR_CTEIF0;
DMA1->LIFCR |= DMA_LIFCR_CHTIF0;
/*Set the source buffer*/ //Memory Address
DMA2_Stream0->M0AR = memo;
/*Set destination buffer*/ //Peripherial Address
DMA2_Stream0->PAR = periph;
/*Set the length*/
DMA2_Stream0->NDTR = len;
/*Set Control options
* Select Stream0_CH0 |
* Prioritu Lvl = High |
* Memory Increment On |
* Circular mode on |
* Direction Per->Mem (0b00)|
* Enable Transfer Complete interrupt
*/
DMA2_Stream0->CR &= ~(DMA_SxCR_CHSEL |
DMA_SxCR_PL |
DMA_SxCR_MSIZE |
DMA_SxCR_PSIZE |
DMA_SxCR_PINC);
DMA2_Stream0->CR |= (set(0,DMA_SxCR_CHSEL_Pos) |
set(2,DMA_SxCR_PL_Pos) |
set(1,DMA_SxCR_MINC_Pos) |
set(1,DMA_SxCR_CIRC_Pos) |
set(0,DMA_SxCR_DIR_Pos) |
set(1,DMA_SxCR_TCIE_Pos)
);
/*Enable direct mode and disable FIFO*/
DMA2_Stream0->FCR = 0;//set(0,DMA_SxFCR_FEIE_Pos);
/*!! Enable DMA1 Stream 6*/
DMA2_Stream0->CR |= set(1,DMA_SxCR_EN_Pos);
/*Enable ADC transmitter DMA*/
ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos);
/*DMA Interrupt enable in NVIC*/
NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
void DMA2_Stream0_IRQHandler(void)
{
/*Check for transfer complete interrupt*/
if(DMA2->LISR & msk(1,DMA_LISR_TCIF0_Pos))
{
//Clear flag
DMA2->LIFCR |= msk(1,DMA_LIFCR_CTCIF0_Pos);
//Callback
dma_ADC_callback();
}
}
static void dma_ADC_callback(void)
{
printf("[ Readings Pots.: | %li | %li ]\r\n", sensor_val[0], sensor_val[1]);
}
int main(void)
{
/*Setup*/
//clock_init_168(); //SYSCLK = 168MHz, AHB = 84MHz, APB1 = 42MHz, APB2 = 84MHz
//init_systick_MS(SYSTICK_LOAD_VAL_MS);
gpio_init();
dma2_stream0_init((uint32_t)&sensor_val, (uint32_t)&ADC1->DR, 2);
adc_init();
adc_start_conversion(); //continuous conversion
char count = 0;
for(;;)
{
debug_msg("count : %d", __PRETTY_FUNCTION__, count);
delayMS(500);
count++;
}
}
I finally found a way to fix it!
First of all, the ADC has to be enabled before the DMA. With the code above, ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos) wouldn't get set because I was trying to set the DMA before the ADC. Hence, my interruption never got called. The correct order of function calls is:
adc_init();
dma2_stream0_init((uint32_t)&sensor_val, (uint32_t)&ADC1->DR, 2);
adc_start_conversion();
However, after ADC_CR2_DMA was getting set, my callback got called, but only once. So I had to disable DMA selection with ACD1->CR2 = ADC_CR2_DDS, so DMA would issue conversion requests recurrently:
/*Enable ADC transmitter DMA*/
ADC1->CR2 |= (set(1,ADC_CR2_DMA_Pos) | //<<make sure ADC is already enabled
set(1,ADC_CR2_DDS_Pos)); //<<without DDS, the DMA does a single conversion
Once I did that, my callback got called and I was reading data, but only sensor_val[0] was being updated. That was happening because I had PSIZE == 00, which is the value used to set MSIZE in direct mode. Since I'm using DMA for ADC (maximum 12-bit resolution), I changed sensor_val to uint16_t and PSIZE = 0b01:
DMA2_Stream0->CR |= (set(0,DMA_SxCR_CHSEL_Pos) |
set(2,DMA_SxCR_PL_Pos) |
set(1,DMA_SxCR_PSIZE_Pos) | //<<sets MSIZE=PSIZE in direct mode
set(1,DMA_SxCR_MINC_Pos) |
set(1,DMA_SxCR_CIRC_Pos) |
set(0,DMA_SxCR_DIR_Pos) |
set(1,DMA_SxCR_TCIE_Pos));
And voilá!
NOTES
PINC should be kept at reset value for this configuration, otherwise ADC will expect data with more than 16-bits.
If you need slower rates of acquisition, instead of setting DDS, a DMA request function can be created where the DMA bit is reset and set again:
void adc_new_dma_conversion(void)
{
/*DMA has to be reset first
* then re-enabled to generate
* a new DMA request
*/
ADC1->CR2 &= ~set(1,ADC_CR2_DMA_Pos);
ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos);
}

STM32 UART2 BRR Not Giving Expected Results

I am currently working with an STM32F407VG on the Discovery board. I am going through the peripherals and trying to get each one working by manipulating the registers only (no HAL).
When I go to initialize UART2 it is transmitting the character I write to the DR but it is doing so ~12x faster than expected, I'm shooting for 9600 baud. I measured this using an oscilloscope (~8-9 us per bit) and playing with the baud rate in Putty (111,111 baud to show the actual character).
I am maxing out the clock speed of the chip 168 MHz SYSCLK, APB1 Prescaler "/4", APB2 Prescaler "/2". I am pretty sure my clocks are at what they are supposed to be, to verify I set up TIMER12, which shares the APB1 clock and set the prescaler to 8400 and had an interrupt generated every time there was a compare match (CCR = 5000) and overflow. I measured this with the oscilloscope and I am getting a 1 Hz square wave as expected which means that the APB1 for Timer 12 is at 84 MHz.
Here is my clock init code:
void SysClockConfig ( void ){
//setting up the MCO output to see the clock signal
//RCC->CFGR |= ( 6 << 24 ) | (3 << 21 ) | ( 4 << 27 );
//1. ENABLE HSE and wait for the HSE to become Ready
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
// 2. Set the POWER ENABLE CLOCK and VOLTAGE REGULATOR
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;
// 3. Configure the FLASH PREFETCH and the LATENCY Related Settings
FLASH->ACR = FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_5WS;
//4. Configure the PRESCALARS HCLK, PCLK1, PCLK2
// AHB PR
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
// APB1 PR
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
// APB2 PR
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
//5. Configure the MAIN PLL
RCC->PLLCFGR = (PLL_M << RCC_PLLCFGR_PLLM_Pos) | (PLL_N << RCC_PLLCFGR_PLLN_Pos) | (PLL_Q << RCC_PLLCFGR_PLLQ_Pos) | (RCC_PLLCFGR_PLLSRC_HSE);
//6. Enable the PLL and wait for it to become ready
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
//7. Set source
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
This is my UART2 config code:
void UART2_Config ( void )
{
RCC->APB1ENR |= RCC_APB1ENR_USART2EN; //clock UART
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //clock GPIOA
GPIOA->MODER |= ( 2 << GPIO_MODER_MODER2_Pos ) | ( 2 << GPIO_MODER_MODER3_Pos ); //set PA2 and PA3 alternate function
GPIOA->OSPEEDR |= ( 3 << GPIO_OSPEEDR_OSPEED2_Pos ) | ( 3 << GPIO_OSPEEDR_OSPEED3_Pos ); //clock GPIO pin at fastest speed
GPIOA->AFR[0] |= ( 7 << GPIO_AFRL_AFSEL2_Pos ) | ( 7 << GPIO_AFRL_AFSEL3_Pos ); //set PA2 and PA3 to alt func UART2
USART2->CR1 = 0;
USART2->CR1 |= USART_CR1_UE; //UART Enable
//USART2->BRR = (0x16 << USART_BRR_DIV_Mantissa_Pos) | (0xc << USART_BRR_DIV_Fraction_Pos); //Set Baud rate
USART2->BRR = 4300;
//USART2->CR1 |= USART_CR1_RE; //Receiver enable
USART2->CR1 |= USART_CR1_TE; //Transmitter enable
//Baud rate is off by a factor of 12ish
}
Finally, my main and while loop with the function for sending a character:
void UART2_SendChar ( uint8_t c )
{
USART2->DR = c;
while ( !(USART_SR_TC));
}
int main ( void )
{
SysClockConfig();
GPIO_Config();
TIM10_Config();
TIM12_Config();
UART2_Config();
//NVIC_SetPriority (TIM1_UP_TIM10_IRQn, 1);
//NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
NVIC_SetPriority (TIM8_BRK_TIM12_IRQn, 1);
NVIC_EnableIRQ(TIM8_BRK_TIM12_IRQn);
while ( 1 )
{
//GPIOD->BSRR |= (1<<12);
//delay(2000000);
//GPIOD->BSRR |= (1<<28);
delay(4000000);
UART2_SendChar('a');
}
}
I can't find anything in the reference manual to explain this behavior. That tells me I am doing something wrong but I can't seem to track it down. On a final note, I had Putty set up to receive 9600 baud and played around with the BRR and setting it to a value of 4300 output the desired character. Plugging that value into the equation for baud in the reference manual gave me an insane system clock 660 MHz, again telling me I am missing something probably pretty obvious.
I've answered a similar question here recently, so let me copy that one:
BRR is a Q12.4 format fixed point number, which needs one additional operation when OVER8 mode is selected.
OP's question is about STM32F4, but if one inspects STM32F030 reference manual, he/she can find the same logic but represented by a different formula. IMO, that one (F030) is more clear and easier to understand.
To convert uint16_t (Q16.0) to Q12.4, you need to multiply it by 16.
Basically, for OVER16 case, BRR is simply becomes PeripheralClock / BaudRate.
For OVER8 case, you first need to calculate a temporary variable as temp = 2 * PeripheralClock / BaudRate. When writing it into BRR, you need to right-shift its lower 4 bits once.
See the example code:
void setBaudRate(uint32_t baud, bool over8)
{
const uint32_t pClock = 42000000ull; // Hard-code or call a function to obtain it
uint32_t usartDiv = (over8) ? (2 * pClock / baud) : (pClock / baud);
uint32_t reg = USART1->BRR;
reg &= ~0xffff; // Clear the lower 16 bits
if (over8) {
reg |= (usartDiv & 0xfff0) | ((usartDiv & 0xf) >> 1);
}
else { // over16
reg |= usartDiv;
}
USART1->BRR = reg;
}
So, for your case, the correct value of BRR is 4375.

How to refer to a specific GPIO pin in an Arduino Uno using C code?

I am trying to design a 4-bit adder. I've set my input and output ports but am not sure where to go from there.
My "B" input port is Port D, my "A" input port as well as my "Cin" is Port B, and my "S" output port as well as my "Cout" is Port C. I am having trouble figuring out how to isolate the individual ports (such as the carry ripple) and am pretty much out of ideas aside from nested if-statements.
My code is currently as follows:
#include <avr/io.h>//library used to access the pin addresses
int main () {
DDRD &= ~(0b00111100);//B inputs
DDRB &= ~(0b00011111);//Carry-in + A inputs
DDRC |= 0b00011111;//Carry-out + S outputs
while (1) {
//PORTC |= PIND + PINB;
//PORTC &= ~(PIND + PINB);
if ((PIND & 0b00000000)&&(PINB & 0b00000000)) {
PORTC |= 0b00000000;
PORTC &= ~(0b00000000);
}
else if ((((PIND & 0b00000100)||(PIND & 0b00001000)||(PIND & 0b00010000)||(PIND & 0b00100000))&&(PINB & 0b00000000))||(((PINB & 0b00000100)||(PINB & 0b00001000)||(PINB & 0b00010000)||(PINB & 0b00100000))&&(PIND & 0b00000000)))
PORTC |= 0b00000001;
PORTC &= ~(0b11111110);
}
}
return 0;
}
If you want to refer to a specific pin/pins then it's relatively easy (assuming the registers are set as outputs):
For example, to isolate pin 4 on PORTD you do:
PORTD |= (1 << PIND4);
This will set pin 4 in PORTD to HIGH.
PORTD |= (1 << PIND4) | (1 << PIND5);
This will set pins 4 and 5 in PORTD to HIGH.
PORTD &= ~(1 << PIND4);
This will set pin 4 in PORTD to LOW.
PORTD &= ~(1 << PIND4) & ~(1 << (PIND5);
This will set pins 4 and 5 in PORTD to LOW.
You can also define a macro for the (1 << n) logic:
#define _BV(n) (1 << (n))
These tutorial have it all explained pretty well: http://maxembedded.com/2011/06/port-operations-in-avr/ and https://efundies.com/avr-bitwise-operations-in-c/.
If you go through the bitwise logic step by step on a sheet of paper it will become clearer!

AVR ATMega328P ADC channel selection issue

I'm tinkering around with an ATMega328P right now and wanted to read an analogue value from a pin through the ADC and simply output the value to 4 LEDs. Really simple
#define F_CPU 20000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define BRIGHTNESS_PIN 2
#define ADC_SAMPLES 5
void init_adc()
{
//set ADC VRef to AVCC
ADMUX |= (1 << REFS0);
//enable ADC and set pre-scaler to 128
ADCSRA = (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2) | (1 << ADEN);
}
uint16_t read_adc(unsigned char channel)
{
//clear lower 4 bits of ADMUX and select ADC channel according to argument
ADMUX &= (0xF0);
ADMUX |= (channel & 0x0F); //set channel, limit channel selection to lower 4 bits
//start ADC conversion
ADCSRA |= (1 << ADSC);
//wait for conversion to finish
while(!(ADCSRA & (1 << ADIF)));
ADCSRA |= (1 << ADIF); //reset as required
return ADC;
}
int main(void)
{
uint32_t brightness_total;
uint16_t brightness = 0;
uint32_t i = 0;
init_adc();
sei();
while (1)
{
//reset LED pins
PORTB &= ~(1 << PINB0);
PORTD &= ~(1 << PIND7);
PORTD &= ~(1 << PIND6);
PORTD &= ~(1 << PIND5);
PORTB |= (1 << PINB1); //just blink
read_adc(BRIGHTNESS_PIN); //first throw-away read
//read n sample values from the ADC and average them out
brightness_total = 0;
for(i = 0; i < ADC_SAMPLES; ++i)
{
brightness_total += read_adc(BRIGHTNESS_PIN);
}
brightness = brightness_total / ADC_SAMPLES;
//set pins for LEDs depending on read value.
if(brightness > 768)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
PORTD |= (1 << PIND6);
PORTD |= (1 << PIND5);
}
else if (brightness <= 768 && brightness > 512)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
PORTD |= (1 << PIND6);
}
else if (brightness <= 512 && brightness > 256)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
}
else if (brightness <= 256 && brightness >=64)
{
PORTB |= (1 << PINB0);
}
_delay_ms(500);
PORTB &= ~(1 << PINB1); //just blink
_delay_ms(500);
}
}
This works kind of fine, except the channel selection. When I select a channel it works fine, but independently from the selected channel, channel 0 also always reads and converts. What I mean with that is that if I plug the cable into the selected channel pin, it reads the values correctly. When I plug it into any other channel pin it obviously doesn't, except for ADC0. No matter what channel I set, not only does that one read but also ADC0.
Why is that and how do I fix that?
I already checked my PCB for solder bridges, but there are none and I would also expect some slightly different behaviour with that.
Also ADC4 and ADC5 don't seem to properly convert either. Any idea why that is? The only clue I found in the datasheet is, that those two use digital power, while all the other ADCs use analogue. What's the difference, why does it matter and why does it not correctly convert my anlogue signal?
Both ARef and AVCC are connected according to the datasheet, with the exception that the inductor for ARef is missing.
I think what is happening is that
ADMUX &= (0xF0);
is setting the channel to 0, and
ADMUX |= (channel & 0x0F);
is then setting the channel to the one you want. You're then taking a reading and throwing the result away, which should mean that the initial channel being set to 0 doesn't matter.
Howevever, when you then try to take an actual reading, you are setting the channel again, by using read_adc to take the reading. So, you don't ever throw a reading away.
What I would do is replace your ADMUX setting commands with:
ADMUX = (0xF0) | (channel & 0x0F)
Then move this into a separate function called something like set_adc_channel(int channel). Include a throw away read in that function, then remove the ADMUX setting from your read_adc function. Just start a conversion and get the result.
Also note that since you only ever use one channel, you could move the channel setting part to init_adc(). I assume it's in a separate function so you could later read more than one channel.
I hope that's clear. Let me know if not.
EDIT: So as you stated, ADIF is really reset by writing logic 1.
I've just tested your adc_read function and it is working for me (if you don't mind Arduino mixture)
uint16_t read_adc(unsigned char channel)
{
//clear lower 4 bits of ADMUX and select ADC channel according to argument
ADMUX &= (0xF0);
ADMUX |= (channel & 0x0F); //set channel, limit channel selection to lower 4 bits
//start ADC conversion
ADCSRA |= (1 << ADSC);
//wait for conversion to finish
while(!(ADCSRA & (1 << ADIF)));
ADCSRA |= (1 << ADIF); //reset as required
return ADC;
}
void setup() {
Serial.begin(57600);
//set ADC VRef to AVCC
ADMUX |= (1 << REFS0);
//enable ADC and set pre-scaler to 128
ADCSRA = (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2) | (1 << ADEN);
pinMode(A0, INPUT_PULLUP);
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP);
}
void loop() {
Serial.println(read_adc(0));
Serial.println(read_adc(1));
Serial.println(read_adc(2));
Serial.println(read_adc(3));
delay(1000);
}
I just connect one of these channels to 3.3V pin and it'll read 713 on it. Other channels are pulled up to levels about 1017.

Resources