Получение данных с акселерометра LIS2DH12 на микроконтроллере stm32 используя библиотеку "HAL", SPI+DMA
Задача: получить с акселерометра LIS2DH12 данные по SPI, используя DMA, то есть освободить процессорное время на сбор данных, ведь используется два буфера - один обрабатывается, другой заполняется. Известно, что в регистре 0x28 находится младший байт оси X, в регистре 0x29 - младший байт оси Y и т. д. до оси Z. Необходимо получить все 3 оси, т. е. 6 байт. Таких данных должно быть 2048, то есть объявлен массивы:
#define SAMPLE_COUNT 2048
uint8_t sample_buf1[SAMPLE_COUNT * 6] = { 0 };
uint8_t sample_buf2[SAMPLE_COUNT * 6] = { 0 };
uint16_t current_sample = 0;
uint8_t current_buf = 0;
uint8_t *sample_buf[2] = {sample_buf1, sample_buf2};
Суть в том, что я не до конца понимаю, как организовать прием-передачу данных, используя функцию-HAL HAL_SPI_TransmitReceive_DMA(&hspi1, buff_source, buff_destination, 6);. Должен ли я считывать 7 байт вместо 6, ведь первый байт - неверный? Должен ли я использовать режим Normal вместо Circular? К тому же, если я использую прерывание void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi), то код находится в этом прерывании 90% времени, что логично, но как реализовать иначе - возникает трудности.
Ответы (1 шт):
В общем, проблема была решена давно, но руки дошли поделиться только сейчас.
Цифровые акселерометры типа LIS2... имеют на борту вывод прерывания, в данном случае 11 (INT2) и 12 (INT1). Использоваться они могут по-разному, в том числе для уведомления микроконтроллера о готовности данных (CTRL_REG3, бит I1_ZYXDA). В моем случае прерывание возникало через раз после настройки конфигурации датчика, поэтому для 100% результата о готовности данных нужно хотя бы один раз прочитать содержимое регистров OUT_X_L..OUT_Z_H.
После получения уведомления есть несколько вариантов обработки прерывания. Я копирую данные прямо из временного буфера (емкостью 6 байт) в основной буфер и инкрементирую счетчик измерений на 1. Выглядит это следующим образом:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_1)
{
if(current_sample < SAMPLE_COUNT)
{
memcpy(&data_buffers[flag.current_buffer][6 * current_sample], buffer.data_temp, sizeof(buffer.data_temp));
}
current_sample++; // so 6 bytes its 1 sample
Accel_IO_Read(&dev_ctx, LIS2DH12_OUT_X_L, buffer.data_temp, sizeof(buffer.data_temp)); // restart measurements
}
}
Accel_IO_Read(...) лучше использовать как нотификацию о том, что пора бы собрать данные, а не вызывать как отдельную функцию из прерывания по понятным причинам.
В данном случае я не использую DMA, поскольку для моих задач он хоть и дает рост в производительности, но дополнительно поднимает потребление энергии, что в контексте проекта недопустимо. Однако отвечая на свои же вопросы:
Нужно ли использовать DMA? Да, это ощутимо разгрузит процессор, но в отдельно взятых случаях работа с DMA может быть медленнее, чем без нее.
Должен ли я считывать 7 байт вместо 6? Да, первый полученный байт мусорный.
Должен ли я использовать режим Normal вместо Circular? Да, Circular режим в данном случае не нужен.
Должен ли я инкрементировать адрес DMA? Нет, поскольку LIS2DH12 обладает multiple reading (28 стр. datasheet), самостоятельно инкрементировать адрес периферии не нужно.
Алгоритм сбора данных по SPI с использованием DMA и прерывания по INT1 о готовности данных выглядит примерно так:
uint8_t buffer_source[7] = {0};
uint8_t buffer_destination[2048] = {0};
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static uint8_t temp_buffer[7];
temp_buffer[0] = 0x28 | 0x40 | 0x80;
CS_DOWN();
HAL_SPI_TransmitReceive_DMA(&hspi1, buffer_source, temp_buffer, 7);
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
CS_UP();
memcpy(buffer_destination + count_measures*6, buffer_source + 1, 6); // + 1 поскольку первый байт мусорный
count_measures++;
}
int main(void)
{
LIS2DH12_get_data();
while(1)
{
processing_data();
}
}
Чтоб в режиме Normal Mode сработал такой callback, необходимо включить глобальное прерывание SPI.