I am trying to erase one page in flash on an STM32F103RB like so:
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR | FLASH_FLAG_OPTERR);
FLASHStatus = FLASH_ErasePage(Page);
However, FLASH_ErasePage fails producing FLASH_ERROR_WRP
Manually enabling/disabling write protection in the stm32-linker tool doesn't fix the problem.
Basically FLASH_ErasePage fails with WRP error without trying to do anything if there's previous WRP error in the status register.
What comes to your FLASH_ClearFlag call, at least FLASH_FLAG_BSY will cause assert_param(IS_FLASH_CLEAR_FLAG(FLASH_FLAG)); to fail (though I'm not really sure what happens in this case).
#define IS_FLASH_CLEAR_FLAG(FLAG) ((((FLAG) & (uint32_t)0xFFFFC0FD) == 0x00000000) && ((FLAG) != 0x00000000))
What is your page address ? Which address are you trying to access ?
For instance, this example is tested on STM32F100C8 in terms of not only erasing but also writing data correctly.
http://www.ozturkibrahim.com/TR/eeprom-emulation-on-stm32/
If using the HAL driver, your code might look like this (cut'n paste from an real project)
static HAL_StatusTypeDef Erase_Main_Program ()
{
FLASH_EraseInitTypeDef ins;
uint32_t sectorerror;
ins.TypeErase = FLASH_TYPEERASE_SECTORS;
ins.Banks = FLASH_BANK_1; /* Do not care, used for mass-erase */
#warning We currently erase from sector 2 (only keep 64KB of flash for boot))
ins.Sector = FLASH_SECTOR_4;
ins.NbSectors = 4;
ins.VoltageRange = FLASH_VOLTAGE_RANGE_3; /* voltage-range defines how big blocks can be erased at the same time */
return HAL_FLASHEx_Erase (&ins, §orerror);
}
The internal function in the HAL driver that actually does the work
void FLASH_Erase_Sector(uint32_t Sector, uint8_t VoltageRange)
{
uint32_t tmp_psize = 0U;
/* Check the parameters */
assert_param(IS_FLASH_SECTOR(Sector));
assert_param(IS_VOLTAGERANGE(VoltageRange));
if(VoltageRange == FLASH_VOLTAGE_RANGE_1)
{
tmp_psize = FLASH_PSIZE_BYTE;
}
else if(VoltageRange == FLASH_VOLTAGE_RANGE_2)
{
tmp_psize = FLASH_PSIZE_HALF_WORD;
}
else if(VoltageRange == FLASH_VOLTAGE_RANGE_3)
{
tmp_psize = FLASH_PSIZE_WORD;
}
else
{
tmp_psize = FLASH_PSIZE_DOUBLE_WORD;
}
/* If the previous operation is completed, proceed to erase the sector */
CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE);
FLASH->CR |= tmp_psize;
CLEAR_BIT(FLASH->CR, FLASH_CR_SNB);
FLASH->CR |= FLASH_CR_SER | (Sector << POSITION_VAL(FLASH_CR_SNB));
FLASH->CR |= FLASH_CR_STRT;
}
Second thing to check. Is interrupts enabled, and is there any hardware access between the unlock call and the erase call?
I hope this helps
Related
What I want to accomplish
So I want to accomplish the following:
I have 3 FreeRTOS-Threads which all shall read one of 3 (5) channels of my ADC. I want to poll the ADC. The Threads then enter the read value into a FreeRTOS-queue.
My code so far
I have the following functions:
ADC initialisation
void MX_ADC_Init(void)
{
hadc.Instance = ADC;
hadc.Init.Resolution = ADC_RESOLUTION_12B;
hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV4;
hadc.Init.ScanConvMode = DISABLE;
hadc.Init.ContinuousConvMode = DISABLE;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc.Init.NbrOfConversion = 1;
hadc.Init.DMAContinuousRequests = DISABLE;
hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc.Init.LowPowerAutoPowerOff = DISABLE;
hadc.Init.LowPowerAutoWait = DISABLE;
if (HAL_ADC_Init(&hadc) != HAL_OK)
{
Error_Handler();
}
for(int ch = 0; ch < GPIO_AI_COUNT; ch++)
{
ADC_Select_Ch(ch);
}
}
GPIO initialisation
GPIO_InitTypeDef GpioInitStruct = {0};
GpioInitStruct.Pin = GPIO_AI1_PIN | GPIO_AI2_PIN | GPIO_AI3_PIN | GPIO_AI4_PIN | GPIO_AI5_PIN;
GpioInitStruct.Pull = GPIO_NOPULL;
GpioInitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GpioInitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOB, &GpioInitStruct);
Where the GPIO_AI2_PIN definition is defined as:
/* Analog Inputs ----------------------------------------------------------- */
#define GPIO_AI_COUNT 5
#define GPIO_AI1_PIN GPIO_PIN_3
#define GPIO_AI1_PORT GPIOB
#define GPIO_AI1_CH ADC_CHANNEL_2 /* ADC_IN2, Datasheet P. 51 */
#define GPIO_AI2_PIN GPIO_PIN_4
#define GPIO_AI2_PORT GPIOB
#define GPIO_AI2_CH ADC_CHANNEL_3 /* ADC_IN3, Datasheet P. 51 */
#define GPIO_AI3_PIN GPIO_PIN_14
#define GPIO_AI3_PORT GPIOB
#define GPIO_AI3_CH ADC_CHANNEL_1 /* ADC_IN1, Datasheet P. 55 */
#define GPIO_AI4_PIN GPIO_PIN_13
#define GPIO_AI4_PORT GPIOB
#define GPIO_AI4_CH ADC_CHANNEL_0 /* ADC_IN0, Datasheet P. 55 */
#define GPIO_AI5_PIN GPIO_PIN_2
#define GPIO_AI5_PORT GPIOB
#define GPIO_AI5_CH ADC_CHANNEL_4 /* ADC_IN4, Datasheet P. 54 */
Changing channel
void ADC_Select_Ch(uint8_t channelNb)
{
adcConf.Rank = ADC_RANKS[channelNb];
adcConf.Channel = GPIO_AI_CH[channelNb];
adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
{
Error_Handler();
}
}
Where ADC_RANKS and GPIO_AI_CH are static arrays of the channels and ranks I want to use. The ranks increase with every channel.
Reading a channel
uint32_t ADC_Read_Ch(uint8_t channelNb)
{
uint32_t adc_value = 0;
ADC_Select_Ch(channelNb);
HAL_ADC_Start(&hadc);
if(HAL_OK == HAL_ADC_PollForConversion(&hadc, ADC_CONVERSION_TIMEOUT))
{
adc_value = HAL_ADC_GetValue(&hadc);
}
HAL_ADC_Stop(&hadc);
printf("Ch%d / %x) %d\r\n", channelNb, adcConf.Channel, adc_value);
return adc_value;
}
The problem
No matter what I try, the ADC only ever reads in the channel before the last channel I defined. Every time a conversion happens, the method HAL_ADC_GetValue(...) returns only the value of one channel, one, which I haven't even "selected" with my method.
What I've tried so far
I tried several different things:
Change NumberOfConversions
Change ScanMode, ContinuousConvMode, Overrun, EOCSelection, etc.
Use only Rank "1" when choosing a channel
Not use HAL_ADC_Stop(...), that however resulted in a failure (error handler was called)
Using the read functions etc. in the main(), not in a FreeRTOS thread - this also resulted in only one channel being read.
Change GPIO setup
Make the adcConfig global and public, so that maybe the config is shared among the channel selections.
Different clock settings
"Disabling" all other channels but the one I want to use (*)
Several other things which I've already forgotten
There seems to be one big thing I completely miss. Most of the examples are with one of the STM32Fxx microcontrollers, so maybe the ADC hardware is not the same and I can't do it this way. However, since I am using HAL, I should be able to do it this way. It would be weird, if it wouldn't be somehow the same across different uC families.
I really want to use polling, and ask one channel of the ADC by using some kind of channel selection, so that I can read them in different FreeRTOS tasks.
Disabling channels
I tried "disabling" channels but the one I've used with this function:
void ADC_Select_Ch(uint8_t channelNb)
{
for(int ch = 0; ch < GPIO_AI_COUNT; ch++)
{
adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
adcConf.Channel = GPIO_AI_CH[ch];
adcConf.Rank = ADC_RANK_NONE;
if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
{
Error_Handler();
}
}
adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
adcConf.Channel = GPIO_AI_CH[channelNb];
if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
{
Error_Handler();
}
}
Can anyone help me? I'm really stuck, and the Reference Manual does not provide a good "guide" on how to use it. Only technical information, lol.
Thank you!
I think your general approach seems reasonable. I've done something similar on a project (for an STM32F0), where I had to switch the ADC between two channels. I think you do need to disable the unused channels. Here is some code verbatim from my project:
static void configure_channel_as( uint32_t channel, uint32_t rank )
{
ADC_ChannelConfTypeDef sConfig = { 0 };
sConfig.Channel = channel;
sConfig.Rank = rank;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if ( HAL_ADC_ConfigChannel ( &hadc, &sConfig ) != HAL_OK )
{
dprintf ( "Failed to configure channel\r\n" );
}
}
void adc_configure_for_head( void )
{
configure_channel_as ( ADC_CHANNEL_0, ADC_RANK_CHANNEL_NUMBER );
configure_channel_as ( ADC_CHANNEL_6, ADC_RANK_NONE );
}
void adc_configure_for_voltage( void )
{
configure_channel_as ( ADC_CHANNEL_6, ADC_RANK_CHANNEL_NUMBER );
configure_channel_as ( ADC_CHANNEL_0, ADC_RANK_NONE );
}
uint16_t adc_read_single_sample( void )
{
uint16_t result;
if ( HAL_ADC_Start ( &hadc ) != HAL_OK )
dprintf ( "Failed to start ADC for single sample\r\n" );
if ( HAL_ADC_PollForConversion ( &hadc, 100u ) != HAL_OK )
dprintf ( "ADC conversion didn't complete\r\n" );
result = HAL_ADC_GetValue ( &hadc );
if ( HAL_ADC_Stop ( &hadc ) != HAL_OK )
dprintf ( "Failed to stop DMA\r\n" );
return result;
}
My project was bare-metal (no-OS) and had a single thread. I don't know enough about how your tasks are scheduled, but I'd be concerned that they might be "fighting over" the ADC if there is a chance they could be run concurrently (or pre-empt each other). Make sure the entire configure / read sequence is protected by some kind of mutex or semaphore.
EDIT: I notice a bug in your "Disabling channels" code, where you don't seem to set the rank of your enabled channel. I don't know if that is a transcription error, or an actual bug.
Ranks are used to sort the ADC channels for cases of continuous measurrements or channel scans. HAL_ADC_PollForConversion only works on a single channel and somehow needs to now which channel to pick, therefore it will use the one with the lowest rank. To configure a specific channel to be measured once, set its rank to ADC_REGULAR_RANK_1.
No need to disable any other channels, but remember to properly configure the ranks of all channels if you want to switch to channel scanning or continuous measurements.
HAL_ADC_ConfigChannel(&hadc1, &channel_config) only updates the configuration of the channel itself but does not update the configuration of the ADC peripheral itself. So it has to be understood as "configure this channel" and not as "configure ADC to use this channel"
I am porting LittleFS on STM32 G431Rb internal Flash. Every thing is OK when I read and write file on main function. But when I write some thing in Task, System will be stuck on osdlelay.
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
/* Infinite loop */
int i = 0;
for(;;)
{
ULOG_TRACE("Trace count = %d",i);
i++;
osDelay(5);
}
/* USER CODE END 5 */
}
In ULOG_TRACE function, I called lfs_fs_write function.
lfs_fs_write function call HAL_FLASHEx_Erase and HAL_FLASH_Prog.
static FLASH_EraseInitTypeDef EraseInitStruct;
int stm32_interl_flash_block_erase(const struct lfs_config *c, lfs_block_t block)
{
uint32_t PageError;
__disable_irq();
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP |
FLASH_FLAG_OPERR |
FLASH_FLAG_PROGERR |
FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR |
FLASH_FLAG_SIZERR |
FLASH_FLAG_PGSERR |
FLASH_FLAG_MISERR );
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.Banks = FLASH_BANK_1;
EraseInitStruct.Page = FS_BASE_PAGE_START + block;
EraseInitStruct.NbPages = 1;
if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError)!= HAL_OK){
__enable_irq();
return HAL_FLASH_GetError();
}
HAL_FLASH_Lock();
__enable_irq();
return 0;
}
int stm32_interl_flash_block_prog(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size)
{
__disable_irq();
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP |
FLASH_FLAG_OPERR |
FLASH_FLAG_PROGERR |
FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR |
FLASH_FLAG_SIZERR |
FLASH_FLAG_PGSERR |
FLASH_FLAG_MISERR );
uint32_t dest_addr = FS_BASE_ADDR + c->block_size*block +off;
uint64_t *pSrc = (uint64_t*)buffer;
uint32_t write_size = 0;
while(write_size < size){
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,dest_addr,*(pSrc)) != HAL_OK){
HAL_FLASH_Lock();
__enable_irq();
return HAL_FLASH_GetError();
}
pSrc++;
dest_addr += 8;
write_size += 8;
}
HAL_FLASH_Lock();
__enable_irq();
return 0;
}
I googled these problem, some guys said the problem is that the task schedule interrupt occur when I erase or prog internal flash.But I add disable_irq, it also have the problem.
Do not disable all interrupts. At least the HAL tick should run further. The HAL_FLASH_* methods depend on them.
Use osKernelSuspend() and osKernelResume() to stop and continue thread scheduling.
Further more check all return codes, also from HAL_FLASH_Unlock.
The flash peripheral is a little nasty on the STM32 controller family.
If something going wrong, either you access addresses unaligned, out of bounds or simple not in the correct order the peripheral set its error bits and the HAL functions do not operate anymore. You already clear the error flags.
I've experienced that checking and clearing the error flags also after HAL_FLASH_Program is always a good idea.
PS: If your code really stuck in osDelay then your cmsis os scheduler interrupt is not running or the scheduler is disabled/stoped.
You haven't specified how long the code gets stuck for or why you think it is stuck in osDelay, but here is one guess:
If you are writing or erasing in the same flash bank as you are executing code from, your code will stop until the operation has completed. This will block all tasks and all interrupts whether they are turned on or off. This is because no instructions can be fetched from flash while it is writing or erasing. A few instructions might still execute if they have already been prefetched.
If you are able to use dual-bank mode you can write one bank while executing from the other, but I think the 128kB parts might only support single bank flash, and others may have dual-bank disabled by default.
The L4 series erases flash using pages (or banks, if you do a full erase).
But I'm having some problem writing after doing page erases, and I'm not sure why.
Just to outline the objective I am storing 6 values starting at 0x08080000 (Page 256)
then I am storing more values from 0x08080800) (page 257) to 0x08085800 (page 267)
There is a single function that I use to erase/write the values at page 256:
void write_bias_flash(int16_t biases[]) {
uint16_t *flash_biases = (uint16_t*) (ADDR_FLASH_PAGE_256);
static FLASH_EraseInitTypeDef EraseInitStruct;
Address = ADDR_FLASH_PAGE_256;
/* Fill EraseInit structure*/
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.Page = 0;
EraseInitStruct.Banks = FLASH_BANK_2;
EraseInitStruct.NbPages = 1;
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) {
serprintf("Error erasing biases at address: 0x%x", Address);
}
for (int8_t bias = 0; bias < 6; bias++) {
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,
Address + bias * sizeof(uint64_t), (uint64_t) biases[bias])
!= HAL_OK)
serprintf("Error writing biases to flash.");
}
HAL_FLASH_Lock();
serprintf("Biases stored in flash.");
}
This work great. No issues.
I have two functions I use to erase/write the data starting at 0x08080800) (page 257):
void Erase_TM_Flash() {
uint8_t *flash = (uint8_t*) (FLASH_USER_START_ADDR);
uint8_t *b = (uint16_t*) (ADDR_FLASH_PAGE_256);
static FLASH_EraseInitTypeDef EraseInitStruct;
Address = FLASH_USER_START_ADDR;
/* Fill EraseInit structure*/
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.Page = 1;
EraseInitStruct.NbPages = 255;
EraseInitStruct.Banks = FLASH_BANK_2;
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) {
serprintf("Error erasing biases at address: 0x%x", Address);
}
HAL_FLASH_Lock();
for (uint16_t i = 0; i< (FLASH_ROW_SIZE * sizeof(uint64_t))*255; i++)
{
if ((uint16_t) *(flash+i) != 255) {
serprintf("0x%x is not erased (%i)", flash+i, (uint16_t) *(flash+i));
}
}
}
void Save_to_Flash(uint32_t *data) {
uint32_t src_addr = (uint32_t) data;
Erase_TM_Flash();
serprintf("Saving to flash...");
HAL_StatusTypeDef HAL_STATUS;
HAL_FLASH_Unlock();
Address = FLASH_USER_START_ADDR;
while (Address < (FLASH_USER_END_ADDR - (FLASH_ROW_SIZE * sizeof(uint64_t)))) {
HAL_STATUS = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST, Address, (uint64_t) src_addr);
if (HAL_STATUS == HAL_OK) {
Address = Addres+ (FLASH_ROW_SIZE * sizeof(uint64_t));
src_addr = src_addr + (FLASH_ROW_SIZE * sizeof(uint64_t));
} else {
serprintf("Error writing flash at address 0x%x. (%i)", Address, HAL_STATUS);
Address = Address + (FLASH_ROW_SIZE * sizeof(uint64_t));
src_addr = src_addr + (FLASH_ROW_SIZE * sizeof(uint64_t));
}
}
HAL_FLASH_Lock();
serprintf("Done");
}
The erase works fine. I verify the values in the debugger (and in the code I check for non-erased pages). But when the saving occurs:
Error writing flash at address 0x8080800. (1)
Error writing flash at address 0x8080900. (1)
Error writing flash at address 0x8080a00. (1)
Error writing flash at address 0x8080b00. (1)
And so on through all the remaining pages.
However, if I erase the entire flash:
void Erase_Flash() {
serprintf("Erasing flash...");
HAL_FLASH_Unlock();
/* Clear OPTVERR bit set on virgin samples */
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
/* Fill EraseInit structure*/
EraseInitStruct.TypeErase = FLASH_TYPEERASE_MASSERASE;
EraseInitStruct.Banks = FLASH_BANK_2;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) {
serprintf("Error erasing flash.");
}
HAL_FLASH_Lock();
serprintf("Done.");
}
Then the writing works like a charm.
HAL_STATUS = 1, which according to the code I found is HAL_ERROR = 0x01U, which isn't entirely helpful.
I am not sure what the difference in, but I am hoping another set of eye on my erasure might reveal the issue.
Thanks!
This issue seems to be related to flash fast programming, which isn't available on all STM32 models.
According to the Reference Manual (RM0351), the flash must be mass erased before using fast programming. Otherwise a Programming Sequence Error occurs, and the PGSERR bit in the FLASH_SR register will be set. See 3.3.7 Flash main memory programming sequences in sections Fast Programming / Programming errors and 3.7.5 Flash status register (FLASH_SR) under Bit 7 PGSERR.
RM0351 Rev 9, 3.3.7 Flash main memory programming sequences, on page 106:
Fast programming
(...)
1. Perform a mass erase of the bank to program. If not, PGSERR is set.
2. (...)
RM0351 Rev 9, 3.3.7 Flash main memory programming sequences, on page 107:
Programming errors
(...)
PGSERR: Programming Sequence Error
PGSERR is set if one of the following conditions occurs:
– (...)
– In the fast programming sequence: the Mass erase is not performed before setting
FSTPG bit.
– (...)
The observed behavior is therefore as expected. - So you could replace your Erase_TM_Flash() function and use Erase_Flash() to mass erase the entire flash bank first. Or, avoid using flash fast programming altogether and use FLASH_Program_DoubleWord() or FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, ...) instead.
Related source files: stm32l4xx_hal_flash.h, stm32l4xx_hal_flash.c
Related post: STM32 - writing and reading flash
I have some trouble to receive data over the USART. What I actually want to achieve ist, that I can receive a command over USART with no specific length (only a maximum possible length). So I use the interrupt routine to check each character received, but I somehow still cannot achieve what I want. The routine is called each time I receive a new character, but somehow HAL_UART_Receive_IT(&huart1,rx_data,buff_size_rx) does not upgrade in realtime, then I don't see the received character when I check rx_data[pointer], but a few time later it is in the rx_data buffer.
What I have so far:
int pointer =0;
...
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if ( USART1->ISR & UART_IT_TXE) {
}
if ( USART1->ISR & UART_IT_RXNE) {
HAL_UART_Receive_IT(&huart1,rx_data,buff_size_rx);
if(rx_data[pointer]=='\0') {
pointer=0;
readCommand(rx_data);
clearBuffer(rx_data,buff_size_rx);
} else {
pointer++;
if(pointer>=buff_size_rx) {
pointer=0;
}
}
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
HAL_UART_Receive_IT() is not meant to be called from an interrupt handler that way, but to initiate receiving a fixed number of bytes via interrupt.
A possible workaround is to check your input buffer after HAL_UART_IRQHandler() completes, i.e. in the /* USER CODE BEGIN USART1_IRQn 1 */ section. When a command is processed, you can reset pRxBuffPtr and RxXferCount in the handle structure to their original values to start from the start of the buffer again.
Another horrible possible workaround would be to call HAL_UART_Receive_IT() with a buffer size of 1, and set up a HAL_UART_RxCpltCallback() handler that checks the received byte each time, and calls HAL_UART_Receive_IT() again when necessary.
Of course you could do it without HAL, as PeterJ and others (always) suggest.
You've already implemented pin and interrupt setup, leave them unchanged at first.
Calculate the UART->BRR value according to the reference manual, or copy the relevant code from hal.
set UART->CR1=USART_CR1_RE|USART_CR1_TE|USART_CR1_UE|USART_CR1_RXNEIE; Now, you are getting interrupts.
In the interrupt function, read UART->SR into a temporary variable, and examine it.
Read UART->DR when there is a received byte waiting, do the error handling otherwise (later).
Get rid of the rest of the HAL calls when the above is working.
Interrupt response and processing time is often critical in embedded applications, and the HAL just wastes a lot of that.
The normal HAL library is not useful for continuous reception or commands with different length.
If you have the complete HAL package installed, you could look at the examples for the LowLevel interface.
Projects\STM32F411RE-Nucleo\Examples_LL\USART\USART_Communication_Rx_IT_Continuous
The main thing is to set you usart to continuous reception:
void Configure_USART(void) {
/* (1) Enable GPIO clock and configures the USART pins *********************/
/* Enable the peripheral clock of GPIO Port */
USARTx_GPIO_CLK_ENABLE();
/* Configure Tx Pin as : Alternate function, High Speed, Push pull, Pull up */
LL_GPIO_SetPinMode(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_MODE_ALTERNATE);
USARTx_SET_TX_GPIO_AF();
LL_GPIO_SetPinSpeed(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
LL_GPIO_SetPinOutputType(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinPull(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_PULL_UP);
/* Configure Rx Pin as : Alternate function, High Speed, Push pull, Pull up */
LL_GPIO_SetPinMode(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_MODE_ALTERNATE);
USARTx_SET_RX_GPIO_AF();
LL_GPIO_SetPinSpeed(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
LL_GPIO_SetPinOutputType(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinPull(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_PULL_UP);
/* (2) NVIC Configuration for USART interrupts */
/* - Set priority for USARTx_IRQn */
/* - Enable USARTx_IRQn */
NVIC_SetPriority(USARTx_IRQn, 0);
NVIC_EnableIRQ(USARTx_IRQn);
/* (3) Enable USART peripheral clock and clock source ***********************/
USARTx_CLK_ENABLE();
/* (4) Configure USART functional parameters ********************************/
/* TX/RX direction */
LL_USART_SetTransferDirection(USARTx_INSTANCE, LL_USART_DIRECTION_TX_RX);
/* 8 data bit, 1 start bit, 1 stop bit, no parity */
LL_USART_ConfigCharacter(USARTx_INSTANCE, LL_USART_DATAWIDTH_8B, LL_USART_PARITY_NONE, LL_USART_STOPBITS_1);
/* No Hardware Flow control */
/* Reset value is LL_USART_HWCONTROL_NONE */
// LL_USART_SetHWFlowCtrl(USARTx_INSTANCE, LL_USART_HWCONTROL_NONE);
/* Oversampling by 16 */
/* Reset value is LL_USART_OVERSAMPLING_16 */
// LL_USART_SetOverSampling(USARTx_INSTANCE, LL_USART_OVERSAMPLING_16);
/* Set Baudrate to 115200 using APB frequency set to 100000000/APB_Div Hz */
/* Frequency available for USART peripheral can also be calculated through LL RCC macro */
/* Ex :
Periphclk = LL_RCC_GetUSARTClockFreq(Instance); or
LL_RCC_GetUARTClockFreq(Instance); depending on USART/UART instance
In this example, Peripheral Clock is expected to be equal to
100000000/APB_Div Hz => equal to SystemCoreClock/APB_Div
*/
LL_USART_SetBaudRate(USARTx_INSTANCE, SystemCoreClock/APB_Div, LL_USART_OVERSAMPLING_16, 115200);
/* (5) Enable USART *********************************************************/
LL_USART_Enable(USARTx_INSTANCE);
}
The USART IT Handler should look like
void USARTx_IRQHandler(void)
{
/* Check RXNE flag value in SR register */
if(LL_USART_IsActiveFlag_RXNE(USARTx_INSTANCE) && LL_USART_IsEnabledIT_RXNE(USARTx_INSTANCE))
{
/* RXNE flag will be cleared by reading of DR register (done in call) */
/* Call function in charge of handling Character reception */
USART_CharReception_Callback();
}
else
{
/* Call Error function */
Error_Callback();
}
}
The last thing to set up is the Callback
void USART_CharReception_Callback(void);
Where you could put the bytes into an buffer and handle it in the main loop or where you want.
Since I stumbled over the problem today and could not find a good solution to it, I like to present a very simple one, using most of the HAL but avoiding the problems described...
Short verison of my approach is:
In the last user code section (for the appropriate USART instance, if using more than one) of void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle), enable the IRQ with:
__HAL_UART_ENABLE_IT(&huartx, UART_IT_RXNE);
Then put the desired code in your interrupt function:
void USART3_IRQHandler(void) {
/* USER CODE BEGIN USART3_IRQn 0 */
CallMyCodeHere();
return; // To avoid calling the default HAL handler at all
// (in case you want to save the time)
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3); // This is the CubeMX generated HAL handler
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
Don't use HAL_UART_Receive_IT anywhere, it will disable the IRQ and you need to re-enable it, if you want to get called with every reception.
The long version could be found in my post here...
Here is the full example of receiving data and idle line detection by interrupts:
Enable the receive interrupts and idle line detection in main.c:
/* USER CODE BEGIN USART2_Init 2 */
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); // enable receive intterupts
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); // enable idle line detection
/* USER CODE END USART2_Init 2 */
Sort out the idle line event from within USARTx_IRQHandler in stm32f4xx_it.c:
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart2); // taken from https://electronics.stackexchange.com/questions/471272/setting-up-stm32-timer-for-uart-idle-detection#comment1353999_480556
uart2_idleHandler();
} else {
uart2_handler();
}
return;
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
Testing:
Create the following handlers:
char my_uart_buffer[256];
int my_uart_buffer_index = 0;
void uart2_handler(void){
char buff;
HAL_UART_Receive (&huart2, (uint8_t *)&buff, 1, 400);
my_uart_buffer[my_uart_buffer_index++] = buff;
}
void uart2_idleHandler(){
my_uart_buffer_index = 0;
}
Open a serial port client in your PC, setup as 115200 baud, 8N1.
Set a breakpoint to uart2_idleHandler().
Send "Hello world".
You can examine your buffer by p/c *my_uart_buffer#20 when breakpoint hits.
Example Project
Here is the full example that runs on STM32F407 Discovery board.
You can make it work using HAL! It may not be as elegant as other implementations but it is doable.
You have to create an error handler function and then the function that calls the HAL_UART_RCV_IT must flush the UART RX whenever there is an overrun error.
In addition, I work with two buffers. While the Interrupt is filling one buffer the main loop is emptying the other.
Here it how it is working well for me:
typedef enum
{
UARTREADY = 0,
UARTBUSY = 1,
UARTDATA = 2,
UARTERROR = 3
} enumUartStatus;
while(1){
if(UARTREADY == isUsart3RxReady()){
Usart3RxBuffer((char *)&SOMRxBytesBuffer[use_buffer_index], RCV_BUFFER_BANK_SIZE); // receive bytes in the raw buffer
if(use_buffer_index == RCV_BUFFER_BANK1_INDEX){
use_buffer_index = RCV_BUFFER_BANK2_INDEX;
rxb1_stats++;
}else{
use_buffer_index = RCV_BUFFER_BANK1_INDEX;
rxb2_stats++;
}
}
}
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if(huart == NULL){
return;
}
if(huart->Instance == USART3){
if(HAL_UART_GetError(huart) == HAL_UART_ERROR_FE){
Usart3Ready = UARTREADY;
}else{
Usart3Ready = UARTERROR;
}
}
}
void Usart3RxBuffer(char *buffer, unsigned short rxbuffersize){
/* Reset transmission flag */
if(Usart3Ready != UARTREADY)
{
return;
}
if(HAL_UART_GetState(&huart3) == HAL_UART_STATE_READY){
/*##-3- Put UART peripheral in reception process ###########################*/
if (HAL_UART_Receive_IT(&huart3, (uint8_t *)buffer, rxbuffersize) != HAL_OK)
{
// TODO: Error_Handler();
DEBUG_TRACE(DEBUG_MSK_MAIN, "UART3 error starting receiver!\r\n");
}else{
// An interrupt HAL_UART_ErrorCallback hit right here !!!!
// There is an overrun error happening here so we have to retry
// this is because we are using the Receive_IT in a continous communication and there is no handshake or flow control
if(Usart3Ready != UARTERROR){
/* Busy waiting to receive bytes */
Usart3Ready = UARTBUSY;
}
}
}
if(Usart3Ready == UARTERROR){
HAL_UART_AbortReceive_IT(&huart3);
Usart3Ready = UARTREADY;
}
}
I am trying to communicate over I2C with a Pololu MinIMU9v2 from a TM4C123GXL Launchpad, but every time I try to write to the bus, I am getting I2C_MASTER_ERR_ADDR_ACK and I2C_MASTER_ERR_DATA_ACK. Printing out the slave address shows that it looks right, so I'm thinking this may be something I may be doing wrong with the use of the TI Launchpad driver library.
Here's the initialization routine:
void
InitI2CBus(void)
{
// Initialize the TM4C I2C hardware for I2C0
SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
SYSCTL_XTAL_16MHZ);
SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
GPIOPinConfigure(GPIO_PB2_I2C0SCL);
GPIOPinConfigure(GPIO_PB3_I2C0SDA);
GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
// Initialize the bus
I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false);
}
Here is the code that attempts to read a byte from the device:
uint8_t
ReadByte(uint8_t slaveAddr, uint8_t subAddr)
{
// Write SUB
slaveAddr |= 1; // Set LSB to writemode
I2CMasterSlaveAddrSet(I2C0_BASE, slaveAddr, false);
I2CMasterDataPut(I2C0_BASE, subAddr);
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
while(I2CMasterBusy(I2C0_BASE)) { }
if (CheckError())
{
return 0;
}
// Read data
slaveAddr &= ~1; // Set LSB to readmode
I2CMasterSlaveAddrSet(I2C0_BASE, slaveAddr, true);
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
while(I2CMasterBusy(I2C0_BASE)) { }
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
while(I2CMasterBusy(I2C0_BASE)) { }
uint8_t response = I2CMasterDataGet(I2C0_BASE);
if (CheckError())
{
return 0;
}
return response;
}
Any ideas what I may be doing wrong?
I was having a heck of a time getting my I2C bus working on this board. I'm not sure if this is your issue, but here's the initialization code I'm using (I'm on I2C2):
1. SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C2);
2. SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
**3. GPIOPinTypeI2CSCL(GPIO_PORTF_BASE, GPIO_PIN_6);**
4. GPIOPinTypeI2C(GPIO_PORTF_BASE, GPIO_PIN_7);
5. GPIOPinConfigure(GPIO_PF6_I2C2SCL);
6. GPIOPinConfigure(GPIO_PF7_I2C2SDA);
7. I2CMasterInitExpClk(I2C2_BASE, SysCtlClockGet(), false);
8. I2CMasterSlaveAddrSet(I2C2_BASE, 0x48, false);
Line 3 was missing from most of the examples I could find, and I noticed it's also missing from your code. Before I added this line, I couldn't get my I2C bus to do anything; after adding it it's at least transferring data.
I'm not sure if this is the source of your issue or not, but thought I'd pass it along in case it helps.