Понимание определения указателя в C

У меня есть общие знания об указателях и их использовании, но я придумал код, использующий приведение указателей (я думаю), которого я никогда не видел и не уверен, что понимаю.

Код представляет собой функцию на языке C (написанную для микроконтроллера STM32F091), которая считывает данные из EEPROM (93c66).

Насколько я понимаю, функция принимает адрес для чтения в качестве аргумента. Значение адреса используется как указатель и отправляется по SPI.

Если связь завершена правильно, она начинает прослушивание фактического значения, отправленного из EEPROM, а затем функция возвращает данные.

unsigned short rd_word(unsigned short add93)
{
  unsigned char *pt = (unsigned char *)(&add93);
  unsigned char error = 0;
  
  CS_HIGH;
  
  add93 &= 0x00ff;
  add93 |= 0x0600;
  
  while (HAL_SPI_Transmit(&hspi2, pt, 1, 1000) != HAL_OK){
    __iar_builtin_no_operation();
  }

  SET_BIT((&hspi2)->Instance->CR1, SPI_CR1_CPHA);
  
  while (HAL_SPI_Receive(&hspi2, pt, 1, 1000) != HAL_OK){
    __iar_builtin_no_operation();
  }
  
  CLEAR_BIT((&hspi2)->Instance->CR1, SPI_CR1_CPHA);

  
  CS_LOW;
  
  return add93;
}

Мой вопрос: что делает код unsigned char *pt = (unsigned char *)(&add93);? Насколько я понимаю, он приводит add93 к указателю unsigned char?

Теперь, если unsigned char *pt является указателем и его значением является фактическое значение памяти, в которой хранится add93, то почему он передается pt, а не add93?

Код

add93 &= 0x00ff;
add93 |= 0x0600;

введите add93 в беззнаковый символ (add93 &= 0x00ff;) и прикрепите начальный код чтения для EEPROM (add93 |= 0x0600;) [0x6 = 110b]

Настройки интерфейса SPI

/* SPI2 init function */
void MX_SPI2_Init(unsigned int dataSize)
{

  /* SPI2 parameter configuration*/
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 7;
  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}
unsigned char *pt = (unsigned char *)(&add93); дать вам указатель на переменную add93, а не на то, куда она «указывает». Для этого используйте unsigned char *pt = (unsigned char *)(add93);, то есть отбросьте оператор-указатель на &.
Some programmer dude 19.04.2024 15:47

Кроме того, оператор типа add93 &= 0x00ff; изменяет значение самой переменной add93, а не ячейку памяти, на которую она «указывает». Для этого используйте *pt &= 0x00ff;

Some programmer dude 19.04.2024 15:49

Так почему бы не передать переменную add93 напрямую, а не использовать указатель pt?

NicoCaldo 19.04.2024 15:50

Да, и если вы хотите читать и писать unsigned short, вам нужно изменить pt на unsigned short *.

Some programmer dude 19.04.2024 15:50

Переменная add93 содержит короткое значение без знака, которое является адресом памяти (я предполагаю), но не является указателем на этот адрес памяти. Возьмем, к примеру, return add93;, который возвращает текущее значение самого add93, а не значение, хранящееся в ячейке памяти.

Some programmer dude 19.04.2024 15:54

@Someprogrammerdude, только что выяснилось, что HAL_SPI_Transmit и HAL_SPI_Receive на самом деле нужен указатель, а не переменная в качестве входных данных. Теперь это имеет смысл

NicoCaldo 19.04.2024 15:55

@NicoCaldo: Что еще более важно, HAL_SPI_Transmit и HAL_SPI_Receive на самом деле нужен char*, а не unsigned short

Mooing Duck 19.04.2024 16:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
82
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В первом вопросе

что делает код unsigned char *pt = (unsigned char *)(&add93);? Насколько я понимаю, он приводит add93 к указателю unsigned char?

Да, строка unsigned char *pt = (unsigned char *)(&add93) передает адрес add93 в указатель на unsigned char

В секунду:

add93 &= 0x00ff;
add93 |= 0x0600;

Они выполняют побитовые операции И и ИЛИ, чтобы подготовить add93 с правильными битами команды и адреса для операции чтения EEPROM.

ОП спросил, «приводит ли это add93 к указателю unsigned char, и ответ — НЕТ. Он приводит адрес значения add93 к указателю unsigned char.

Mooing Duck 19.04.2024 16:59
Ответ принят как подходящий

Указатели в большинстве случаев эквивалентны адресу. Но во встроенных системах это обычно локальные адреса внутри самого микроконтроллера. Здесь вы имеете дело с какой-то внешней памятью.

rd_word — одна из функций драйвера, взаимодействующего с этой памятью. Видимо они решили, что у него должен быть параметр unsigned short add93, соответствующий адресу в этой памяти, но внутри вашего микроконтроллера он выражается в виде простого числа.

Здесь мы можем отметить, что использование unsigned short и подобных «примитивных типов данных», которые по умолчанию входят в состав C, является плохой практикой, особенно во встроенных системах. Потому что мы не знаем, насколько велик этот тип — возможно, он 16 бит. Лучшей практикой, как это делается в профессиональном программном обеспечении, является использование типов из stdint.h, например uint16_t.

Код хочет преобразовать адрес add93 во что-то значимое для оборудования памяти и отправить его через SPI. А SPI почти всегда работает побайтно, по 8 бит за раз. Поэтому нам нужно сериализовать add93 на байтовые фрагменты, чтобы иметь возможность отправить его.

В языке C есть специальное правило, которое позволяет нам побайтно проверять любой тип переменной. Мы можем сделать это, приведя адрес любой переменной к указателю на символьный тип, байтовый тип C. Чаще всего выражается как unsigned char или uint8_t, потому что при работе с необработанными байтами мы не хотим, чтобы какая-либо знаковость испортила ситуацию.

&add93 для этой цели берет адрес локальной переменной add93. Эта переменная находится в локальном стеке микроконтроллера. Приведя адрес к указателю на символ (unsigned char *)(&add93), мы получаем адрес первого байта. Здесь следует отметить некоторые вещи:

  • Внутренняя скобка (&add93) не обязательна.
  • Результирующий указатель будет указывать на самый низкий адрес unsigned short. Соответствует ли это старшему или наименее значимому байту, зависит от порядка байтов ЦП (Что такое порядок байтов ЦП?). Cortex M и STM32 в значительной степени имеют прямой порядок байтов, поэтому здесь у нас есть младший байт.
  • Знание порядка байтов имеет основополагающее значение для программирования встроенных систем. Все протоколы связи и периферийное оборудование, такое как ваша память, могут использовать другой порядок байтов, чем ваш ЦП. Например, обратный порядок байтов очень распространен для сетевых протоколов. Если мы взаимодействуем с сетью или устройством, используя другой порядок байтов, нам придется перетасовать порядок байтов.

Это всего лишь побитовая арифметика:

add93 &= 0x00ff;
add93 |= 0x0600;

Первая строка, выполняющая побитовое И против маски, где все единицы установлены в 0xFF..., называется «маскированием», что означает, что в этом случае младший байт сохраняется, а старший байт очищается. Затем они добавляют значение 0x6 к старшему байте. Напомним, что pt указывает на младший байт, поэтому значение, на которое он указывает, соответствует той части, которая была сохранена с помощью битовой маскировки.

Функция отправки SPI HAL_SPI_Transmit(&hspi2, pt, 1, 1000) предположительно отправляет сюда 1 одиночный байт, тот, на который мы указывали.

SET_BIT((&hspi2)->Instance->CR1, SPI_CR1_CPHA); меняет тактовую фазу связи SPI на лету по какой-либо причине. Делать это в середине передачи — экзотика, но существует множество странных интерфейсов SPI, которые плохо стандартизированы.

Затем он получает 1 байт, который предположительно перезаписывает данные, на которые указывает pt. Это тоже немного странно - мы можем вспомнить, что SPI является полнодуплексным, всегда отправляя во время приема. То есть, если мы сначала что-то отправляем, а затем что-то получаем, на самом деле мы отправляем 2 байта и получаем 2 байта. Я не знаю подробностей о драйвере SPI и почему нам (не) хотелось бы отправлять 2 байта.

Однако мы можем отметить, что старший байт, установленный add93 |= 0x0600, не был отправлен. Что странно. Скорее, функция вернет смесь 0x06 и всего, что было получено от SPI.

Настоятельно рекомендуется просматривать фактически отправляемые данные с помощью осциллографа. Запустите сигнал выбора микросхемы и просмотрите MOSI и/или MISO.


Чтобы ответить на ваши конкретные вопросы:

Мой вопрос: что такое код unsigned char *pt = (unsigned char *)(&add93); делает? Насколько я понимаю, он приводит add93 к указателю на беззнаковый символ?

Нет, как упоминалось выше, он преобразует адрес add93 в байтовый указатель, чтобы мы могли получить доступ к unsigned short побайтно. Это адрес вашего микроконтроллера, не путать с адресом любой части EEPROM, с которой вы имеете дело.

Теперь, если беззнаковый символ *pt является указателем, поэтому его значение является значением фактической памяти, в которой хранится add93, почему передается pt, а не add93?

Потому что функции драйвера SPI, такие как HAL_SPI_Transmit, ожидают в качестве входных данных байтовый указатель на выделенный буфер. Он должен работать побайтно. Они также могли бы передать (unsigned char*)&add93 в функцию, и это было бы эквивалентно. Но они не могли просто пройти &add93, потому что в результате получился бы unsigned short*, тип которого несовместим с unsigned char*.

Вопреки распространенному мнению, дикие и сумасшедшие преобразования указателей на самом деле не разрешены в C, и добиться их корректности сложно. Общий совет новичкам: вообще никогда не делайте заброс, потому что с этим очень легко ошибиться.

приведите add93 к беззнаковому символу (add93 &= 0x00ff;

Нет, нигде не было приведения, это работало непосредственно с беззнаковой короткой переменной.

прикрепите исходный код чтения EEPROM (add93 |= 0x0600;

Кажется, так и было задумано, но не похоже, что эта часть вообще когда-либо отправлялась через SPI. Этот код написан не очень хорошо и, возможно, содержит ошибки. Возможно, в какой-то момент они намеревались увеличить pt, чтобы указать на следующий байт, но это не похоже на это. Я бы сразу сказал, что код просто сломан, но мне придется поближе взглянуть на драйвер SPI, чтобы сказать наверняка.

Большое спасибо за обширное объяснение. Я добавил настройки микроконтроллера и SPI в исходный вопрос. Кроме того, что касается add93 |= 0x0600, это странно, поскольку, согласно таблице данных 93c66, это код OP для команды чтения, за которым следует 0, как показано на рисунке 9-2.

NicoCaldo 19.04.2024 16:56

Другие вопросы по теме