Я работаю над несколькими проектами домашней автоматизации с PIC12F675. Чтобы максимизировать переносимость, я использую операции побитового сдвига для сопоставления GPIO, например:
// Hardware Mapping
#define pressure_pin (1<<0) // Pressue sensor output (analog input)
#define button_pin (1<<1) // Multi-function button (digital input)
#define buzzer_pin (1<<2) // NPN transitor to activate buzzer (digital output)
#define pause_pin (1<<4) // NPN transistor to pause button (digital output)
#define led_pin (1<<6) // Led (digital output)
#define PORT GPIO // Port
Чтобы установить входные контакты («pressure_pin» и «button_pin»), необходимо изменить регистры TRISIO, ANSEL и ADCON0. Изменения TRISIO и ANSEL легко выполнить следующим образом:
TRISIO |= (button_pin | pressure_pin); // Output pins
ANSEL = (0x50 | pressure_pin); // Fosc/16 and 'pressure_pin' as analog input`
У ADCON0 должны быть установлены биты 7 и 0, а биты 3 и 2 должны получать номер GPIO, используемый в качестве аналогового входа, как показано на рисунке ниже (извлечено из таблицы данных PIC12F675).
введите сюда описание изображения
Итак, мне нужно, чтобы ADCON0 получил одно из следующих значений (согласно определению «pressure_pin»):
(1<<0)
0x81
(1<<1)
0x85
(1<<2)
0x89
(1<<3)
0x8D
Мое решение было:
ADCON0 = 0x81; // 0b10000001. ADC value right justified ; ADC turned on
ADCON0 |= (((0x82>>(pressure_pin-1))&0x1)<<2) | (((0x88>>(pressure_pin-1))&0x1)<<3);
Этот фрагмент кода воспроизводит мое решение на языке C:
int main(int, char**)
{
unsigned char pressure_pin = (1<<3);
unsigned char ADCON0 = 0x81;
ADCON0 |= (((0x82>>(pressure_pin-1))&0x1)<<2) | (((0x88>>(pressure_pin-1))&0x1)<<3);
return 0;
}
Учитывая ограничения PIC12F675, существует ли более элегантное и/или более эффективное решение для этого преобразования?
Заранее спасибо.
@Бармар, я думаю, он хочет чего-то такого тривиального. Компилятор не будет знать значение как постоянное выражение
Возможно, таблица поиска. Никаких расчетов не требуется.
const unsigned char data[] = {[1 << 0] = 0x81, [1 << 1] = 0x85, [1 << 2] = 0x89, [1 << 3] = 0x8D};
unsigned char foo(unsigned pressurepin)
{
return data[pressurepin];
}
У меня нет компилятора изображений, но аналогичный 8-битный код, сгенерированный для AVR, показывает, что он будет намного быстрее (и, скорее всего, меньше).
https://godbolt.org/z/j9aWP3Tqx
0___________, спасибо за точный и краткий ответ! Компиляция вашего кода в MPLAB X (XC8) привела к получению ассемблерного кода немного большего размера, чем тот, который был получен с помощью моего решения (мое: 37% использования программной памяти; ваше: 38,3%). Ваше решение работает, более читабельно и занимает почти ту же программную память, что и мое. Код, сгенерированный компилятором AVR, невероятно больше, чем код, сгенерированный XC8!
Насколько я понимаю, ваша цель — иметь возможность устанавливать в регистр только некоторые определенные биты. Я правильно понял?
Если это так, вы можете использовать базовые операции маскировки и сдвига в функции для установки канала АЦП. Я рекомендую вам сделать это в функции, поскольку в вашем приложении может потребоваться изменить канал АЦП более одного раза. Это уменьшит использование ПЗУ. Итак, следующий фрагмент дает вам представление о том, как это будет реализовано: Давайте определим некоторые константы для маскировки и смещения:
#define ADC_CHANNEL_MASK 0xC // or 0b00001100
#define ADC_CHANNEL_OFFSET 2 // bit offset to be used in shift operations
Теперь давайте реализуем функцию ADC_setChannel
:
void ADC_setChannel(char channel) {
// clear the channel bits first
ADCON0 &= ~ADC_CHANNEL_MASK;
// mask the channel parameter for 2 bits first, then left shift the value 2 times
// then or it with the current register value. This way only tha channel bits
// will be modified
ADCON0 |= (channel & 3) << ADC_CHANNEL_OFFSET;
// Delay for 20 micros to comply the acquisition time after changing the channel
// See the ADC section of datasheet for the calculation of acquisition time
__delay_us(20);
}
Однако если ваше приложение в течение всего срока службы использует только один канал АЦП, вам не нужно реализовывать его как функцию. Вместо этого вы должны настроить его один раз в основной функции перед входом в основной цикл. Те же операции, но не в функции. См. следующий фрагмент:
// The definitions made above + your pin definitions
...
void main(void) {
// Other init codes go here...
...
ADCON0 &= ~ADC_CHANNEL_MASK;
ADCON0 |= 2 << ADC_CHANNEL_OFFSET; // Let's say you wanna select 2nd AN channel
__delay_us(20);
...
while(1) {
// Application code
}
}
Но если вы также хотите сопоставить контакты порта с номерами каналов, вам следует использовать простой массив, содержащий номера каналов, соответствующие номеру порта. В этом случае имеется 4 канала AN, входы которых — GP0, GP1, GP2 и GP4 соответственно. И эти цифры сильно различаются в зависимости от модели MCU. Вот почему я не знаю какой-либо формулы для решения этого вопроса о сопоставлении каналов для всех устройств с использованием битовых сдвигов, поскольку размещение битов не является последовательным. Использование массивов более практично для создания такого типа отображения, поскольку числа невелики. Массив будет выглядеть так:
// We add new definitions for ADC
#define GPIO_PIN_COUNT 7
// The indexes represent the GPIO pin numbers. So if the corresponding pin does not have
// analog feature, this array will give us -1.
char getChannelForPin[GPIO_PIN_COUNT] = { 0, 1, 2, -1, 3, -1, -1 };
// Then you would use that array like this in the application
char channel = getChannelForPin[pressure_pin];
if (channel >= 0) {
ADC_setChannel(channel);
}
else {
// Handle; that pin does not have analog feature
}
Козьмотроник, большое спасибо за полный и поучительный ответ. Он, конечно, более читабелен и элегантен, чем мой. Однако использование компиляции (с помощью XC8) упрощенной версии вашего кода привело к использованию 43,2% памяти программы и 67,2% памяти данных. Мое решение использует 37% и 35,9% соответственно. Если хотите, я могу отредактировать ответ, включая проведенные мной тесты. Еще раз спасибо!
Любой разумный компилятор должен выполнить это преобразование автоматически.