Наконец-то отступать больше некуда и пришло время собрать машинку во что-то целое и почти не делимое. Остался один малюсенький вопрос: как эта машинка будет включаться и выключаться? И как быть с традицией продолжать в следующем посту тему предыдущего?
Что бы не нарушать традицию, продолжу тему из предыдущего поста: мигание светодиодиков. Что с ними не так?
Не так с ними многое, начиная от кривой по времени задержки и заканчивая страшным обработчиком прерывания от таймера.
Для начала надо сделать нормальную функцию задержки. Ставим TMR1 на период в 1мс и добавляем в код
volatile uint16_t millis=0;
void DelayMS(uint16_t m)
{
millis=0;
while(millis<m);
}
...
void TMR1_ISR(void)
{
millis++;
...
Таким нехитрым образом мы получим не зависящую от частоты/наличия другого кода функцию задержки (главное, что бы таймер тикал) до 65 секунд. Для наших целей хватит с большим запасом.
Теперь смотрим на код зажигания светодиодов. Как-то некрасиво в прерывании делать задержки (да еще циклом), плюс куча сравнений, которые по-хорошему надо вынести куда-нибудь подальше. Нет, для домашнего проекта вполне себе, но для промышленного — никуда не годится.
Давайте снова изменим алгоритм. В чем суть? Рассмотрю на примере порта C, который целиком отдан под светодиоды.
Младшие 3 бита у нас отвечают за цвета. Старшие 3 — за первые 3 светодиода. Мысленно включим первый светодиод на красный, второй на зеленый, а третий на «оранжевый» (или красный+зеленый). Что будет в порту?
Первый шаг, «красный» канал
0b00101001
Второй шаг, «зеленый» канал
0b00110010
Третий шаг, «синий» канал
0b00000100
Как видим, нам в принципе достаточно раз за разом отправлять в порт по байту, что бы получить тот же функционал. А формирование этих байт отдадим функции setLed — она вызывается гораздо реже, чем код в прерывании и может позволить себе всякие расчеты, циклы и прочие излишества.
Хорошо, что мы знаем, что можем пихать в порт С всякое — у него 2 старших бита никуда не подключены (если судить по даташиту и коду). Но что делать с портом А, у которого нам надо менять только один бит, не трогая остальных? Для этого можно воспользоваться замечательной функцией «битовое И» и «битовое ИЛИ».
Что бы было понятней, распишу на примере:
0b00001010 Читаем из порта текущее значение. Нужный нам бит я выделил — нам надо изменить только его значение. Остальные случайно расставил.
Делаем «битовое ИЛИ» (где-то должна быть 1, что бы в результате получилась тоже 1) с заранее подготовленной функцией setLed маской. Для включения бита она должна быть 0b00100000. Скажем, нам надо включить бит
0b00001010 | 0b0010000 = 0b00101010
Все, нужный бит установлен, остальные не тронуты, так что можем записывать полученное значение назад в порт.
А теперь тот же самый бит сбросим с помощью «битового И» (и там и там должна быть 1, что бы на выходе тоже 1)
0b00101010 & 0b11011111 = 0b00001010
Вроде все логично. Переписываем код.
В общем, на функцию setLed возлагается задача готовить 6 байта того, что мы будем отдавать в порт. Меняем led.c и led.h
// basic colors
#define RED 1
#define GREEN 2
#define BLUE 3
#define OFF 0
extern volatile uint8_t lc[3];
extern volatile uint8_t la[3];
void setLed(uint8_t n,uint8_t c)
{
uint8_t b;
if(n==0) b=0b11110111;
if(n==1) b=0b11101111;
if(n==2) b=0b11011111;
if(c>0)
{
c--;
if(n==3)
{
la[0]=1;
la[1]=1;
la[2]=1;
la[c]=0;
}
else
{
lc[c]=lc[c]&b;
}
}
else
{ // turn off channel N
b=b^0xff;
if(n==3)
{
la[0]=1;
la[1]=1;
la[2]=1;
}
else
{
lc[0]=lc[0]|b;
lc[1]=lc[1]|b;
lc[2]=lc[2]|b;
}
}
И в обработчике прерывания прописываем
LATC=lc[current_led];
_LED4_ = la[current_led];
current_led++;
if(current_led==3) current_led=0;
Согласитесь, гораздо короче предыдущего варианта (правда, работу с битами я оставил для порта С, а порт А оказалось проще «вручную» дергать). Раз короче, значит, выполняется быстрее. А раз выполняется быстрее, значит общая производительность контроллера тоже выше. Сплошной бонус со всех сторон. Правда, для получения «белого» теперь придется три раза вызвать setLed, но это мелочи.
Теперь осталось решить самый главный вопрос: как её включать и как выключать?
В принципе, у меня есть вариант, предложенный изначальными создателями машинки — кнопка на двери. Но прежде чем заниматься прикладным творчеством, надо прикинуть «а стоит ли»?
Пишем следующий код
setLed(3, RED);
setLed(2, BLUE);
PWM3_LoadDutyValue(49*2);
TMR2_LoadPeriodRegister(49);
uint8_t c,q;
for(q=0;q<1;q++) {
setLed(0, RED);
setLed(1, OFF);
DelayMS(100);
for (c = 0; c < 10; c++) {
setLed(0, OFF);
DelayMS(50);
setLed(0, RED);
DelayMS(50);
}
setLed(1, BLUE);
setLed(0, OFF);
DelayMS(100);
for (c = 0; c < 10; c++) {
setLed(1, OFF);
DelayMS(50);
setLed(1, BLUE);
DelayMS(50);
}
}
PWM3_LoadDutyValue(0);
TMR2_LoadPeriodRegister(0);
setLed(0, OFF);
setLed(1, OFF);
setLed(2, OFF);
setLed(3, OFF);
DelayMS(10);
SLEEP();
Обратите внимание на последнюю инструкцию — SLEEP() загоняет микроконтроллер в спячку, когда все выключено и вытащить из этого состояния может только какое-нибудь прерывание. Прерываний нам пока не завезли, поэтому просто включаю-выключаю вручную при измерении.
Подключаю мультиметр для измерения потребления тока. Мой выдал что когда светодиодики горят и динамик ноет, то потребление тока 2.97-3мА. Если не пиликать динамиком, то 2,85-2,9. А когда всё заканчивается, то 0,01мА и меньше (просто у моего мультиметра 0,01мА нижний предел измерений). Разница между тактовой частотой 1МГц и 4МГц тоже минимальна — буквально 0,2мА.
Согласитесь, шикарные цифры для 12 мигающих светодиодов? Если бы мы их подключали «как обычно», то получили бы ток в 30-40мА.
Так как у меня будет 2 батарейки ААА (средняя емкость у них 1000мАч), то легко можно прикинуть, сколько проживет машинка.
Если все время будет пищать и мигать — 1000мАч/3мА/24ч=13 дней.
А если все время спать — 1000/0,1/24= 416 дней.
Понятно, в реальности цифры будут меньше, так как ни одна батарейка не позволит себя разрядить «в ноль». Но и так — год в спячке для машинки очень неплох. В общем, необходимости в отдельном выключателе нет и поэтому я решаю воспользоваться уже приготовленной заготовкой для кнопки. Подключаю кнопку между RA4 и «минусом», в Code Configurator включаем эту ножку на чтение и не забываем включить подтягивающий резистор и генерацию прерываний для этой ножки.
Почему-то в Code Configurator забыли добавить код для разрешения прерываний (хотя галочки ставятся) и из-за этого мне приходится открывать даташит на процессор и искать, какие биты за что отвечают (вообще-то это очень полезное занятие). Включаем разрешение прерываний от периферии и разрешаем подключенному к порту А4 их генерировать.
INTCONbits.IOCIE =1;
IOCANbits.IOCAN4 =1;
И снова проверяем. В результате у нас по нажатию кнопки все просыпается, пару раз мигает и засыпает.
Красивая мигалка уже есть (код выше), а с частотами сирен мне помогли в комментариях. Сирена скорой помощи: частотно-модулированный сигнал треугольной формы, с меняющейся во времени частотой генерации от 850 Гц до 1550 Гц. со скоростью 0.2 герц. Сирена милиции от 650 Гц. до 1350 Гц. И не спрашивайте у меня, как скорость можно мерять в герцах :) Но звук получается очень похожим.
Теперь осталось собрать все наработанное (звук, мигалки, сон) в один кусок кода. В самых лучших традициях обучалок, я оставлю это сделать вам.
Почти готовое можно взять как обычно, у меня multik.org/pic/policecar.rar
Автор: kiltum