In a STM32G4, I was able to setup the DAC DMA such that I could use a regular variable (ie. a uint8_t array). However, when I tried porting my code over to an H723, the DAC DMA would not work unless it's with a constant variable (ie. a const uint8_t array) that is set before runtime. My application requires runtime changes to the array. A pointer initialization of the array does not seem to work. I was wondering if there is a way to remedy this? Am I stuck with the constant variable? Thank you!
EDIT1: Current Setup of DAC DMA and TIMER
static void MX_DAC1_Init(void){
DAC_ChannelConfTypeDef sConfig = {0};
hdac1.Instance = DAC1;
if (HAL_DAC_Init(&hdac1) != HAL_OK){
Error_Handler();
}
sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
sConfig.DAC_Trigger = DAC_TRIGGER_T15_TRGO;
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_ENABLE;
sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
}
Timer15 Config:
static void MX_TIM15_Init(void)
{ TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim15.Instance = TIM15;
htim15.Init.Prescaler = 55-1;
htim15.Init.CounterMode = TIM_COUNTERMODE_UP;
htim15.Init.Period = 10-1;
htim15.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim15.Init.RepetitionCounter = 0;
htim15.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim15) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim15, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
DMA Config:
static void MX_DMA_Init(void){
__HAL_RCC_DMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);
/* DMAMUX1_OVR_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMAMUX1_OVR_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMAMUX1_OVR_IRQn);
}
in the main function:
int main(void){
MX_DAC1_Init();
MX_TIM15_Init();
MX_OPAMP2_Init();
/* USER CODE BEGIN 2 */
set_sine(dac_data1, NUM_DAC_POINTS) //Set a max amplitude uniformly over number of points, dac_data is initialized as uint8_t dac_data1[NUM_DAC_POINTS];
HAL_TIM_Base_Start(&htim15); //Start the timer for DAC DMA Transfer
HAL_DAC_Init(&hdac1);
(HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_2, (uint32_t *)dac_data1, NUM_DAC_POINTS, DAC_ALIGN_8B_R);
}
This setup does not work, but just by initialzing dac_data1 as const uint8_t and predefining it, the DMA works.
Ok, Thanks to Tom V for the insight on the Different Memory Banks. In my case, dac_data1 is placed on RAM, also known as DTCMRAM on the reference manual. DTCMRAM is not accessible to DMA but is chosen to be the default memory location for runtime operations on the H7. A modification in the linker files (In my case, both ..._FLASH.ld and ..._RAM.ld) were needed. I changed the .bss, .data and ._user_heap_stack memory location from RAM to RAM_D1. This was taken directly from: https://community.st.com/s/article/FAQ-DMA-is-not-working-on-STM32H7-devices
Related
The goal is to read multiple ADC channels by polling. It does not need to be fast - the idea is to read the voltages from different batteries that are attached. I have a STM32L071 microcontroller. The programming is a bit different compared to the STM32F0 model. I am using platformio.
I found already very helpful information here:
Individually read distinct inputs with STM32F0 ADC
https://controllerstech.com/stm32-adc-multiple-channels/
https://deepbluembedded.com/stm32-adc-tutorial-complete-guide-with-examples/
However, unfortunately I cannot read out multiple channels. The problems are probably related to HAL_ADC_Init and HAL_ADC_ConfigChannel.
Here is a minimal code example:
#include <Arduino.h>
#include <STM32IntRef.h>
uint32_t a1=0, a2=0;
#define HAL_ADC_MODULE_ENABLED
ADC_HandleTypeDef hadc1;
void displaying(){
Serial.println("values:");
Serial.println("-------");
Serial.print("ch1 - ");
Serial.println(a1);
Serial.print("ch2 - ");
Serial.println(a2);
Serial.println("");
}
void config_ext_channel_ADC(uint32_t channel, bool val) {
hadc1.Instance = ADC1;
hadc1.Init.SamplingTime = ADC_SAMPLETIME_79CYCLES_5;
HAL_ADC_Init(&hadc1);
ADC_ChannelConfTypeDef sConfig;
sConfig.Channel = channel;
if (val == true) {
sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
} else {
sConfig.Rank = ADC_RANK_NONE;
}
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
Serial.println("Error ADC Config Channel");
//Error_Handler();
}
}
uint32_t r_single_ext_channel_ADC(uint32_t channel) {
/* read the ADC and output result */
uint32_t digital_result;
config_ext_channel_ADC(channel, true);
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 1000);
digital_result = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
config_ext_channel_ADC(channel, false);
return digital_result;
}
void readBat() {
/* read voltages */
a1 = r_single_ext_channel_ADC(1);
a2 = r_single_ext_channel_ADC(PA2);
}
void setup() {
// put your setup code here, to run once:
// Serial monitor
Serial.begin(9600);
Serial.println(F("Starting now"));
// initialize pins for ADC
analogReadResolution(ADC_RESOLUTION);
pinMode(PA1, INPUT);
//pinMode(BATTERY_SENSE_PIN2, INPUT);
pinMode(PA2, INPUT_ANALOG);
}
void loop() {
// put your main code here, to run repeatedly:
readBat();
displaying();
delay(2000);
}
The output is:
values:
-------
ch1 - 0
ch2 - 140
Sounds reasonable, but applying some voltages at the pins does not change the values.
Could somebody please provide me some advice, ideas?
Okay, in the meantime I found the problem. The software is fine; I took the Arduino framework and used analogRead().
I needed to clean all the solder flux from the PCB which caused some contacts between the pins.
Moreover, if one wants to use address the HAL directly, one should set the ClockDivider such that the ADC samples below 1 MHz.
Code to read different ADC channels (here ADC2 with 2 channels) separetly (Generated By CubeMX):
Note: We have to use ScanConvMode plus DiscontinuousConvMode and NO ContinuousConvMode.
/* ADC2 init function */
void MX_ADC2_Init(void)
{
/* USER CODE BEGIN ADC2_Init 0 */
/* USER CODE END ADC2_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC2_Init 1 */
/* USER CODE END ADC2_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc2.Instance = ADC2;
hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc2.Init.Resolution = ADC_RESOLUTION_12B;
hadc2.Init.ScanConvMode = ENABLE;
hadc2.Init.ContinuousConvMode = DISABLE;
hadc2.Init.DiscontinuousConvMode = ENABLE;
hadc2.Init.NbrOfDiscConversion = 1;
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc2.Init.NbrOfConversion = 2;
hadc2.Init.DMAContinuousRequests = DISABLE;
hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc2) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_8;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_9;
sConfig.Rank = 2;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC2_Init 2 */
/* USER CODE END ADC2_Init 2 */
}
Now, to read:
void readChannel(){
//read the next channel
HAL_ADC_Start(&hadc2);
uint8_t ret = HAL_ADC_PollForConversion(&hadc2, 1000 /*timeout*/);
uint16_t value = HAL_ADC_GetValue(hadc);
printf("HAL_ADC_PollForConversion status: %d, VALLLL: %d\n", ret, value);
}
int main(){
while(1){
//Automatically read the first channel (channel 8):
readChannel();
HAL_Delay(100);
//Automatically read the second channel (channel 9):
readChannel();
HAL_Delay(100);
}
}
Did you miss to set the io to analog in mode? It should be something like this somewhere (usually in your hal msp file, adc_init func).
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
edit => you missed it for ch1
analogReadResolution(ADC_RESOLUTION);
pinMode(PA1, INPUT);
//pinMode(BATTERY_SENSE_PIN2, INPUT);
pinMode(PA2, INPUT_ANALOG);
It should be the same as PA2, input is "digital mode".
I am using STM32CUBEMX to generate codes. In the example (Examples\UART\UART_Printf) of STM32WB55, I did not find __HAL_RCC_USART1_CLK_ENABLE(). I am very confused why it is not necessary to enable USART1 clock, but we need to enable GPIOB clock?
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_ODD;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
}
Both calls are necessary.
__HAL_RCC_USART1_CLK_ENABLE is invoked from HAL_UART_MspInit (see stm32wbxx_hal_msp.c), which in turn is invoked from HAL_UART_Init
This is the way, how library and Cube samples are organized. Higher level code fills main descriptor (huart1 in your case) with pointer to the periphery instance, base initialization parameters and calls the Init function with this descriptor. The Init function performs register initialization, but first calls the MspInit function, that must be implemented in the application code. The MspInit function performs additional initialization like enabling clocks, GPIO configuration, etc. All what doesn't fit a library code
MspInit is also implemented as empty function with weak attribute in the library by default, which means any other implementation will override the default one.
No PWM output for a timer on a stm32f4discovery board whatsoever. Straight line on oscilloscope.
Note: code was generated in Stm32cubeide except the "user code" part
Main Function
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
/* USER CODE END 2 */
while (1)
{
}
}
Initialization of TIM2
static void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 100;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim2);
}
I've also tried instead of HAL_TIM_PWM_Start, HAL_TIM_PWM_Start_IT,and HAL_TIM_PWM_Start_DMA
Thanks
You should set Pulse value greater than zero and less than your period value; it is duty of your PWM. Zero duty generates zero out.
You have to add these lines that I've written below here;
...
uint16_t pwm_val; //Define the value to determine duty cycle - pwm_val = <some value>
while (1)
{
/* This is going to start pwm out */
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_val);
}
...
I am new I2C communication. I examined some running code. And I used function their used. But I can't get any data. I wonder if I2C have to do initial config? Where is the problem. This is function I wrote:
void GetI2CAccelerometer(uint8_t slaveAddress,uint8_t accelData[6])
{
// slaveAddress=0x68 (default address is written in datasheet)
HAL_I2C_Master_Transmit(&hi2c1,slaveAddress<<1,1,0x3B,1,200);
HAL_I2C_Master_Receive(&hi2c1,slaveAddress<<1,accelData,6,200);
HAL_I2C_Mem_Read(&hi2c1,(slaveAddress<<1)+1,0x3B,1,accelData,1,200);
// i tried this function too but not working
}
I created this project with CubeMX. This is initial I2C config and also GPIO_A clock line is activated in another function which I did not write:
static void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 208;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
First of all, better use DMA or IT data exchange. Polling is not good, but ok for testing.
You must put pointers to data, not the data itself. The good practice is something like this:
void GetI2CAccelerometer(I2C_HandleTypeDef * hi2c, uint8_t slaveAddress, uint8_t * accelData, size_t size) {
uint8_t request = 0x3B;
// slaveAddress=0x68 (default address is written in datasheet)
// HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
while (HAL_I2C_Master_Transmit(hi2c, slaveAddress << 1, (uint8_t*)request, 1, 200) != HAL_OK) {
// Use FreeRTOS vTaskDelay(1) and/or check errors here
// And check timeout or you will hang over here
}
//HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
while (HAL_I2C_Master_Receive(hi2c, slaveAddress << 1, accelData, size, 200) != HAL_OK) {
// Use FreeRTOS vTaskDelay(1) and/or check errors here
// And check timeout or you will hang over here
}
while (HAL_I2C_GetState(hi2c) != HAL_I2C_STATE_READY) {
// Use FreeRTOS vTaskDelay(1) and/or check errors here
// And check timeout or you will hang over here
}
}
And then request data by function:
uint8_t accelData[6];
GetI2CAccelerometer(&hi2c1, 0x68, accelData, 6);
Thats ok for testing, but bad for production. Get the data and then rewrite to use I2C DMA or IT. Check errors at I2C bus and make some enum to return errors/states from function.
So I wanted to read out multiple channels from adc3 on my stm32f7 discovery. I have been able to read out one channel and set up for multiple ones, but I can't figure out how to read out per channel. I wanted to read them out by interrupt so I set the adc up like this:
hadc3.Instance = ADC3;
hadc3.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV4;
hadc3.Init.Resolution = ADC_RESOLUTION_12B;
hadc3.Init.ScanConvMode = ENABLE; /* Sequencer disabled (ADC conversion on only 1 channel: channel set on rank 1) */
hadc3.Init.ContinuousConvMode = ENABLE; /* Continuous mode enabled to have continuous conversion */
hadc3.Init.DiscontinuousConvMode = DISABLE; /* Parameter discarded because sequencer is disabled */
hadc3.Init.NbrOfDiscConversion = 0;
hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* Conversion start trigged at each external event */
hadc3.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_CC1;
hadc3.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc3.Init.NbrOfConversion = 2;
hadc3.Init.DMAContinuousRequests = DISABLE;
hadc3.Init.EOCSelection = DISABLE;
if (HAL_ADC_Init(&hadc3) != HAL_OK)
{
/* ADC initialization Error */
Error_Handler();
}
/*##-2- Configure ADC regular channel ######################################*/
sConfig.Channel = ADC_CHANNEL_8;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
{
/* Channel Configuration Error */
Error_Handler();
}
/*##-2- Configure ADC regular channel ######################################*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 2;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK)
{
/* Channel Configuration Error */
Error_Handler();
}
/*##-3- Start the conversion process #######################################*/
if(HAL_ADC_Start_IT(&hadc3) != HAL_OK)
{
/* Start Conversation Error */
Error_Handler();
}
and then I have a callback where it will go when end of conversion, here I wanted to read the data out but I don't know how to read out per channel.
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
/* Get the converted value of regular channel */
ADC3ConvertedValue[0] = HAL_ADC_GetValue(AdcHandle);
ADC3ConvertedValue[1] = HAL_ADC_GetValue(AdcHandle);
char disp[50];
sprintf(disp, "%d%%", ADC3ConvertedValue[0]);
BSP_LCD_DisplayStringAtLine(1, (uint8_t*) disp);
char disp1[50];
sprintf(disp1, "%d%%", ADC3ConvertedValue[1]);
BSP_LCD_DisplayStringAtLine(2, (uint8_t*) disp1);
}
can anyone help me with reading this out. I don't want to use the DMA because it conflicts with the LCD.