I have a problem with PGSERR and PGPERR bits being set after reset operation
I am using stm32f4 board than I am using CANBUS and FW update process.
When I use serial debug print in MX_CAN1_Init function. I faced with flash erasing error. Then I analyse that error I found PGSERR and PGPERR bits set.
These bits have to be "0" .
I wanted to analyze this problem so I did these test in my initlize states:
MX_CAN1_Init();
Serialdebugprint("Read PGAERR FLAG %d\n",__HAL_FLASH_GET_FLAG((FLASH_FLAG_PGPERR)));
Serialdebugprint("Read PGSERR FLAG %d\n",__HAL_FLASH_GET_FLAG((FLASH_FLAG_PGSERR)));
I get this value:
Read PGAERR FLAG 64
Read PGSERR FLAG 128
I get this value:
Read PGAERR FLAG 64
Read PGSERR FLAG 128
But these flag has to be 0. After the reset.
If I removed the MX_CAN1_Init these flag equals to "0".
When I changed some parameter in the CAN_Init function for example communication speed to 83.3kps to 1Mbs I get this flag as 0 and 128 . By the way when I remove the serialdebug I get the 0 and 128 again.
If I put the MX_CAN1_Init function after a few function than this flag returns "0" .
What is the reason?
/**
* #brief CAN1 Init function
* #retval null
*/
static void MX_CAN1_Init(void)
{
CAN_FilterTypeDef sFilterConfig;
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 28; //3
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_15TQ; //11
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; //2
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = ENABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/*##-2- Configure the CAN Filter ###########################################*/
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.SlaveStartFilterBank = 0;
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
/* Filter configuration Error */
// SerialPrint("FILTER ERROR !!");
Error_Handler();
}
/*##-3- Start the CAN peripheral ###########################################*/
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
// SerialPrint("CAN START ERROR !!");
/* Start Error */
Error_Handler();
}
/*##-4- Activate CAN RX notification #######################################*/
if(HAL_CAN_ActivateNotification(&hcan1,CAN_IT_TX_MAILBOX_EMPTY | CAN_IT_RX_FIFO0_MSG_PENDING |CAN_IT_BUSOFF) != HAL_OK)
{
Error_Handler();
}
Serialdebugprint("Success CAN Init \n");
}
Serialdebugprint function:
void Serialdebugprint(const char *serial_data, ...)
{
char uartbuffer[1024]="";
va_list arg;
va_start(arg, serial_data);
uint16_t len = vsnprintf(uartbuffer, 1024, serial_data, arg);
va_end(arg);
HAL_UART_Transmit(&huart2, (uint8_t *)uartbuffer, len, 100);
}
I think problem start HAL_UART_Transmit(&huart2, (uint8_t *)uartbuffer, len, 100) after this line. What is the reason for ?
Stumbled over this thread when I debugged a related Problem. At some point in my firmware I needed to erase 1 page in the flash of an STM32L496. All I got from the STM HAL was an HAL_ERROR. Further investigations showed that PGSERR Flag was set from a previous flash write process. In the end I found out that the error flag was set because a memset operation to address 0x00.
meaning the PGSERR Flag can be set if you have code like this:
uint8_t* pointer;
memset(pointer, 0x00, 10);
btw. there in my case there was no Hartfault when running that code.
char uartbuffer[1024]=""; means "please give me a stack overflow asap". Don't declare huge buffers like that on the stack, move them to file scope and/or declare as static.
The stack overflow destroys your call stack and pretty much everything else too, after which your code starts to run or access addresses in la-la-land, leading to PGSERR.
To fix this problem, you need to add __HAL_FLASH_CLEAR_FLAG(0xFF); before HAL_FLASH_Unlock();
Related
I'm developing a project with STM32F4 and I need to enable the UART6 receive interrupt. I have used STM32CubeMX to enable the UART6 and in the STM32CubeMX I have enabled the NVIC USART6 global interrupt.
The example UART_HyperTerminal_IT
I have followed the example UART_HyperTerminal_IT which is installed by the package STM32Cube_FW_F4_V1.27.0.
The code is more recent but not so different from this example code.
I would like to modify the example to send back every character received from UART6 on the same UART6 (echo). So I have removed all the code of the example that sends data by the UART and wait data from the UART.
I have initialized the UART6 by the following code:
UartHandle.Instance = USART6;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
if(HAL_UART_Init(&UartHandle) != HAL_OK) {
/* Initialization Error */
Error_Handler();
}
The previous code is very similar to the example code (I have changed only baud rate and the parity).
My interrupt routine
I have defined the function USART6_IRQHandler in the file stm32f4xx_it.c as in the code below:
void USART6_IRQHandler(void) {
unsigned char ch;
uint32_t isrflags = READ_REG(huart6.Instance->SR);
uint32_t cr1its = READ_REG(huart6.Instance->CR1);
if (((isrflags & USART_CR1_RXNEIE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)){
huart6.Instance->SR;
ch = huart6.Instance->DR;
HAL_UART_Transmit(&huart6, &ch, 1, 1000);
}
HAL_UART_IRQHandler(&huart6);
}
In the readme file of the example I have found this sentence:
This example shows how to ensure UART Data buffer transmission and reception with
Interrupt.
The sentence says that in the example the Interrupt is enabled, but if I try to send data to the UART6 the interrupt routine USART6_IRQHandler() is not called.
I think that the UART6 Receive Interrupt is not enabled, but how can I enable it? Is there a specific bit in some register that must be set?
Thanks
This post on SO speak about this topic but I think is too old for my STM32F4.
This other post is old too.
Thanks to #pmacfarlane's comment I solved the problem of the lack of reception interrupts in the example UART_HyperTerminal_IT.
So I confirm that the only modification needed to obtain the echo of the characters receives from UART6 is the addition of the instruction huart6.Instance->CR1 |= USART_CR1_RXNEIE;.
Initialization of UART6 with reception interrupts enabled
So the initialization code for the UART6 becomes:
huart6.Instance = USART6;
huart6.Init.BaudRate = 115200;
huart6.Init.WordLength = UART_WORDLENGTH_8B;
huart6.Init.StopBits = UART_STOPBITS_1;
huart6.Init.Parity = UART_PARITY_NONE;
huart6.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart6.Init.Mode = UART_MODE_TX_RX;
huart6.Init.OverSampling = UART_OVERSAMPLING_16;
if(HAL_UART_Init(&huart6) != HAL_OK) {
/* Initialization Error */
Error_Handler();
}
// ---> enable reception interruptions
huart6.Instance->CR1 |= USART_CR1_RXNEIE;
Note. In the previous code I have substituted the variable UART_HandleTypeDef UartHandle used in the UART_HyperTerminal_IT example with the variable:
UART_HandleTypeDef huart6;
Interrupt routine implements echo
The code of the interrupt routine presented in the question remains exactly the same:
void USART6_IRQHandler(void) {
unsigned char ch;
uint32_t isrflags = READ_REG(huart6.Instance->SR);
uint32_t cr1its = READ_REG(huart6.Instance->CR1);
if (((isrflags & USART_CR1_RXNEIE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)){
huart6.Instance->SR;
ch = huart6.Instance->DR;
HAL_UART_Transmit(&huart6, &ch, 1, 1000);
}
HAL_UART_IRQHandler(&huart6);
}
The USART6_IRQHandler() function retrieves the received char and sends it immediately back implementing the echo of every char received.
While loop empty
That's all, the while(1) loop is completly empty:
while(1) {
// empty loop
}
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 tried to send Buffer from stm32f407 to terminal.Type is uint8_t and array is 15000. My code is works. but when i try to send 2 buffer like 5000 array, there is problem for second buffer. I use normal mode.What i should do to sending succesfully?
/* USART3 DMA Init */
/* USART3_TX Init */
hdma_usart3_tx.Instance = DMA1_Stream3;
hdma_usart3_tx.Init.Channel = DMA_CHANNEL_4;
hdma_usart3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart3_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart3_tx.Init.Mode = DMA_NORMAL;
hdma_usart3_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_usart3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart3_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(huart,hdmatx,hdma_usart3_tx);
/* USART3 interrupt Init */
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
/* USER CODE BEGIN USART3_MspInit 1 */
/* USER CODE END USART3_MspInit 1 */
void Send_Dataa(void)
{
HAL_UART_Transmit_DMA(wifi_uart, buf1, 5000);
HAL_UART_Transmit_DMA(wifi_uart, buf2, 5000);
}
If you check the return code from HAL_UART_Transmit_DMA(), you'll probably see it is returning HAL_BUSY. (Note you should always be checking the return codes from HAL functions.)
You'll need to wait until the first transmit is complete before starting the second transmit. You can use HAL_UART_GetState() to monitor the state of the UART.
The HAL does not chain together multiple DMA requests.
I am using an STM32G030C8T6 as an SPI (Slave device) which receives characters from a Raspberry pi (Master device), receiving works perfectly! no problems there, however when i try to send data from STM32 to Pi, it seems that the STM32 gets stuck for a while and Pi rx buffer is filled with only one bit in repeat e.g., if i send char buf[6] = {0,1,2,3,4,5}; Pi receives (111111) or (333333) depending on how many characters i am sending.
What do i actually want to do?
I want to transmit ADC data from STM32(slave mode) to Pi(master mode), so far it only receives one bit i repeat.
Can someone please help me achieve this?
Here's my SPI config:
void MX_SPI1_Init(void)
{
/* USER CODE BEGIN SPI1_Init 0 */
/* USER CODE END SPI1_Init 0 */
/* USER CODE BEGIN SPI1_Init 1 */
/* USER CODE END SPI1_Init 1 */
/* SPI1 parameter configuration*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_SLAVE;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI1_Init 2 */
/* USER CODE END SPI1_Init 2 */
}
then the functions to read characters, send data and string
char SPI_read(void)
{
// SPI1->SR is the STATUS REGISTER on SPI1 Bus
// SPI1->DR is the DATA REGISTER on SPI1 Bus
char data;
while(!(SPI1->SR & SPI_SR_RXNE));
while(SPI1->SR & SPI_SR_BSY);
data = SPI1->DR;
printmsg("%c",data);
return data;
}
void spi_Send(char caracSend)
{
while(!(SPI1->SR & SPI_SR_TXE));
SPI1->DR = caracSend;
}
void spi_send_string(char* stringSend)
{
int i=0;
unsigned int sizeChar = 0;
sizeChar = strlen(stringSend);
__NOP();
for(i=0;i<sizeChar;i++)
{
spi_Send(stringSend[i]);
}
}
Here's my function to receive data from Pi which i call in the main while loop.
void SPI_Receive_Commands(void)
{
while(HAL_GPIO_ReadPin(SPI_SS_GPIO_Port, SPI_SS_Pin) == GPIO_PIN_RESET);
{
HAL_SPI_Receive(&hspi1, (uint8_t *)spi_buf, 10, 100);
if(spi_buf[0] == 'v')
{
memset(spi_buf,'*',10);
printmsg("Character V received\r\n");
Battery_Voltage();
spi_send_string(batteryVoltage);
spi_Send('v');
printmsg("Sending battery voltage\r\n");
}
}
}
Thank you so much for helping me out in advance.
Initialisation. You need set the RX FIFO threshold to 1/4 as you read and write bytes. I do not know how to do it in HAL. Register level simply set FRXTH bit in the CR2 register
DR register has to be accessed in 8 bits operations.
You do not need to use BSY bit in polling mode unless you want to disable the SPI peripheral.
void spi_Send(char caracSend)
{
while(!(SPI1->SR & SPI_SR_TXE));
*(volatile uint8_t *)&SPI1->DR = caracSend;
}
void spi_send_string(char* str)
{
if(str) while(*str) spi_Send(*str++);
}
char SPI_read(void)
{
char data;
while(!(SPI1->SR & SPI_SR_RXNE));
data = *(volatile uint8_t *)&SPI1->DR;
return data;
}
Remember that raspberry pi HAS TO send dummy data to allow slave to send information. Slave does not generate the clock signal and clock has to be supplied by the Master
I'm trying to write and read from an external EEPROM. There is a start bit (SB) followed by an opcode, then a 6-bit address and then the actual data. I've combined the SB and opcode into one byte that I can send as a start condition. I'm able to enable, erase and then write to the EEPROM. I'm assuming this is working since the HAL functions return HAL_OK and I can see the valid waveforms on the scope.
What I can't seem to do is read the data back. For the READ operation I don't see any waveforms on the scope. The number of clock cycles required is odd-numbered and not in multiples of 8. I don't know how I can send odd number of clock cycles since all the data is either 8, 16 or 32-bit. Wherever there are 25 or 29 clock cycles need, I seem to be sending 32 and where the required cycles are 9, I seem to be sending 16. I'm really hoping to avoid bit-banging as suggested in this thread.
Here is the main code:
int main(void)
{
HAL_Init();
MX_GPIO_Init();
MX_SPI1_Init();
__HAL_SPI_ENABLE(&hspi1);
// pull the CS pin high to select the EEPROM (active HIGH)
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
HAL_Delay(10);
// Enable the EEPROM
enable_status = Enable_EEPROM(&EEPROM_SPI_PORT);
HAL_Delay(10);
// Erase the value at address 0x00
erase_status = Erase_EEPROM(&EEPROM_SPI_PORT, addr);
HAL_Delay(10);
// Write data 0xABCD at addr 0x00
write_status = Write_EEPROM(&EEPROM_SPI_PORT, addr, tx_data);
HAL_Delay(10);
// Disabling the EEPROM (with an EWDS) after a WRITE as described in the datasheet
disable_status = Disable_EEPROM(&EEPROM_SPI_PORT);
HAL_Delay(10);
// Re-enabling it
enable_status = Enable_EEPROM(&EEPROM_SPI_PORT);
HAL_Delay(10);
// Read from the EEPROM. This part isn't working.
read_status = Read_EEPROM(&EEPROM_SPI_PORT, addr, rx_data);
HAL_Delay(10);
// Pull the CS pin low to deselect the chip again.
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
while (1)
{
}
}
The SPI is initialized to handle 16-bit data values
SPI_HandleTypeDef hspi1;
/* SPI1 init function */
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
These are the EEPROM functions
#define ERASE 0x07 // erase specific memory location. This is followed by the 8-bit address and then by the 16-bit data.
#define READ 0x06 // read the memory location.
#define WRITE 0x05 // write to the memory location
#define EEPROM_SPI_PORT hspi1
extern SPI_HandleTypeDef EEPROM_SPI_PORT;
//Enable the EEPROM
//Accepts: SPI handle
//Returns: Success or failure of the enable operation
uint8_t Enable_EEPROM (SPI_TypeDef *spi_handle) {
uint16_t ewen = (0x04 << 8) | 0b00110000;
if (HAL_SPI_Transmit(spi_handle, &ewen, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Disable the EEPROM
//Accepts: SPI handle
//Returns: Success or failure of the disable operation
uint8_t Disable_EEPROM (SPI_TypeDef *spi_handle) {
uint16_t ewds = (0x04 << 8) | 0b00000000;
if (HAL_SPI_Transmit(spi_handle, &ewds, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Read from the EEPROM
//Accepts: SPI handle, memory address and data buffer where the read value will be stored
//Returns: Success or failure of read operation
uint8_t Read_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint16_t data) {
uint16_t write_package;
write_package = (READ << 8 | addr);
// if (HAL_SPI_Transmit(spi_handle, &write_package, 1, HAL_MAX_DELAY) == HAL_OK) {
// HAL_Delay(10);
// if (HAL_SPI_Receive(spi_handle, &data, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
// else return FALSE;
// }
if (HAL_SPI_TransmitReceive(spi_handle, &write_package, &data, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Write to the EEPROM
//Accepts: SPI handle, memory address and data to be written
//Returns: Success or failure of write operation
uint8_t Write_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint16_t data) {
uint16_t write_package[2];
write_package[0] = (WRITE << 8 | addr);
write_package[1] = data;
if (HAL_SPI_Transmit(spi_handle, write_package, 2, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Erase a specific memory address from the EEPROM
//Accepts: SPI handle and the memory address to be erased
//Returns: Success or failure of erase operation
uint8_t Erase_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr) {
uint16_t write_package;
write_package = (ERASE << 8 | addr);
if (HAL_SPI_Transmit(spi_handle, &write_package, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
EDIT: I’ve attached waveforms here as well.
Enable
Erase
Write
Without looking through your code in detail, I've spotted a possible problem: In order to complete an SPI operation, the chip select (CS) line usually needs to be pulled low before and set high again after every operation.
So, the EEPROM functions in your driver code probably need to first set the CS pin low, do some SPI operation, and set it high again after that.
For convenience, I usually add some simple helper functions to the driver source file:
static GPIO_TypeDef *_cs_port;
static uint16_t _cs_pin;
static void _chip_select(void)
{
HAL_GPIO_WritePin(_cs_port, _cs_pin, GPIO_PIN_RESET);
}
static void _chip_deselect(void)
{
HAL_GPIO_WritePin(_cs_port, _cs_pin, GPIO_PIN_SET);
}
In that case, I usually intialize the driver and and keep track of the peripheral instance and chip select GPIO, similar to this:
static SPI_HandleTypeDef *_spi;
static uint8_t _init = 0;
int8_t eeprom_init(
SPI_HandleTypeDef *spi,
GPIO_TypeDef *gpio_cs_port,
uint16_t gpio_cs_pin)
{
if (_init)
return -1;
_spi = spi;
_cs_port = gpio_cs_port;
_cs_pin = gpio_cs_pin;
/* do initialization here */
_chip_deselect();
_init = 1;
return 0;
}
int8_t eeprom_clear(void)
{
if (!_init)
return -1;
/* do de-initialization here */
_spi = 0;
_cs_port = 0;
_cs_pin = 0;
_init = 0;
return 0;
}
int8_t eeprom_op_x(void)
{
if (!_init)
return -1;
_chip_select();
op_x(); /* todo */
_chip_deselect();
return 0;
}
I hope this helps :) ! There might be other issues in your hardware/software; this is probably not the full solution to your problem.
BTW: There are also ways to use hardware chip select (STM32 SPI peripheral), which I've never used (SPI / NSS in the reference manual). As far as I can tell, you also used SPI_NSS_SOFT in your SPI configuration, which requires you to manually set the chip select line.
BTW: Unrelated, but maybe of interest: ST provides simple HAL functions to access external I2C flash (HAL_I2C_Mem_*() functions).
edit 0 (more findings by skimming through code / datasheet):
Read_EEPROM() will not work like this, the data read from the bus isn't accessible outside the function's scope (C issue). Instead, a pointer to a read buffer could be passed to the function (or the read data could be returned as return value). For example like this: uint8_t Read_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint8_t *data, uint8_t byte_count)
In Read_EEPROM(): HAL_SPI_TransmitReceive() won't read the incoming bytes, when used like this. It receives and transmits at the same time. So it would make sense to first write the read / address command, and then start reading the incoming bytes (like in your code that has been commented out).
In Enable_/Disable_/Read_/Erase_EEPROM(): The number of bytes (size) seems to be wrong, it should be 2 instead of 1, in order to make HAL_SPI_Transmit() / HAL_SPI_TransmitReceive() transmit/receive the right number of bytes.
This IC does not seem to be well suited to be used with normal
SPI, since it requires a very specific bit sequence which is
not byte aligned (like you said). It might make sense to bit bang
the communication (like you've mentioned), and pay attention to every
little bit stated in the datasheet...
Since this seems to be an early test, I'd try to keep it as simple as possible, and get a first enable/write/read operation going, by bit-twiddling the same SPI pins by hand (reconfigured as normal GPIOs), so that the problems with the STM32's byte oriented SPI HAL functions won't get in your way. And then work towards a nice little driver... Maybe the STM32's SPI can still be used in some way, it's hard to tell for me right now...