Дело такое: писал я себе на ассемблере и горя не знал. Ну за исключением того, что это ассемблер и надо всё делать ручками.
Потом у меня был долгий перерыв в программировании. Сейчас опять занялся, но решил писать на Си. Всё равно я не пишу сложных программ и все мои устройства носят скорее вспомогательный характер, нежели целевой продукт производства. Что думаю париться со стеком, регистровыми парами и прочими делами, если можно на Си всё по быстрому писать.
Ну так вот. Делаю простенькое устройство. Первое что решил написать – это часть кода, которая будет управлять трехразрядным семисегментным индикатором, схема которого ОА. Индикатор управляется через 2 последовательно соединенных сдвиговых регистра 74HC595D.
Казалось бы, чего тут проще?
На схеме я убрал всё лишнее, чтобы не загромождать. Да, не обращайте внимания, что я как-то странно назвал линии управления сдвиговым регистром. Схему сделал давно (на ассемблере всё работало без каких-либо проблем. Там я вообще по 1 проводу всеми тремя ногами сдвигового регистра управлял).
Короче говоря, сдвиговые регистры подключены последовательно, то есть биты пройдя первый насквозь, попадают во второй. Выводы управления регистрами запараллелены между собой.
Используется :
линия данных (Data) (состояние с которой считывается в регистр),
линия записи лог состояния с Data (ReadData) и
линия вывода считанного байта на порт регистра (ShowData).
Линии управления (Data, ReadData, ShowData) напрямую подключены к микроконтроллеру ATtiny2313A. На всякий случай оговорюсь, что земли
регистров и контроллера, также как и линия питания – общие. Еще один момент – выводы регистра Q0-Q7, которые подключены к катодам индикатора A-H могут не соответствовать тем кодам, которые лежат в массиве кодов индикатора в программе. Это связано с тем, что как я уже говорил, схема была сделана давно, а сейчас просто доработана и переложена на другую плату.
Теперь идём к программе.
Проект С (не С++), Atmel Studio 7.0. Оптимизатор настроен на –О1. Чтобы исключить возможность ошибки или опечатки, я приложу скриншот.
Теперь суть задачи, на которой возникла проблема (вообще конечная цель – динамическая индикация, но проблема возникла на ТУПОМ ВЫВОДЕ БИТ В РЕГИСТР).
Напомню, как управлять регистром.
1.Кладем в 0 линии управления ReadData (SH_CP) и ShowData (ST_CP)
2.На линию Data (DS) выводим тот логический уровень, который должен быть занесен в регистр.
3.Поднимаем ReadData (SH_CP) в ЛОГ1, чуточку ждем, кладем ReadData (SH_CP) в ЛОГ0. Это приводит к считыванию бита с Data (DS) в регистр.
Повторяем пункты 2-3 нужное количество раз. В моем случае 16. Нужно заполнить 2 восьмибитных регистра.
4. Поднимаем ShowData (ST_CP) в ЛОГ1, чуточку ждем, кладем ShowData (ST_CP) в ЛОГ0. Это приводит к выводу записанных в регистр бит на порт регистра.
В принципе, всё очень просто, совершенно тупое дерганье ногами.
В моем случае один регистр управляет катодами индикатора. Второй регистр через транзисторы управляет общими анодами. Как уже говорилось, нужно отправлять в них два байта.
Я решил сделать следующим образом:
Код: Выделить всё
volatile unsigned char Kod_Znaka = 0; // Код цифры
volatile unsigned char Kod_Vklucheniya = 0; // Код разряда
volatile unsigned char i = 0; // Счетчик, который используется сдвиговым регистром
volatile unsigned int SRData = 0; // 2 байта, выводящиеся в регистр.
volatile я дописал в процессе схождения с ума из-за не понимания происходящего.
Сделать i глобальной переменной я решил по той же причине.
Из массива с кодами символов я получаю какой-то код. Например для цифры 5:
Код: Выделить всё
Kod_Znaka = digits[5];
//Второй байт несет в себе код, который откроет транзистор нужного мне разряда.
Kod_Vklucheniya = 0x01; // 0b00000001
//Из них я складываю двухбайтное число:
SRData = (Kod_Vklucheniya << 8) + Kod_Znaka;
//До этого момента все идет хорошо, я смотрел в симуляторе.
//Если, например,
//Kod_Vklucheniya == 0b11001100
//Kod_Znaka == 0b00001111
//То SRData == 0b1100110000001111
//Далее простецкий цикл вывода SRData по биту в регистр:
// Shift register
#define PORT_DATA PORTD
#define PinData PIND1
#define PinReadData PIND3
#define PinShowData PIND4
// Определения находятся наверху программы. Привожу их чтобы вы не решили, что я их забыл
Далее код вывода бит в регистр:
Код: Выделить всё
for (i=0; i<16; i++) // 16 раз, для каждого бита повторяем одно и то же
{
if (SRData & 0x80)// Чему равен старший бит, тому будет равен вывод Data (DS)
{
PORT_DATA |= (1<<PinData);
}
else
{
PORT_DATA &= (~(1<<PinData));
}
_delay_us(100); // Чуть ждем хз зачем
PORT_DATA |= (1<<PinReadData); // Запись бита в сдвиговый регистр
_delay_us(100);
PORT_DATA &= (~(1<<PinReadData));
SRData1 <<= 1;// Сдвиг битов влево, чтобы получить следующий бит
}
PORT_DATA |= (1<<PinShowData); // Вывод данных в порт регистра
_delay_us(100);
PORT_DATA &= (~(1<<PinShowData));Это, в принципе весь код, который тут должен отработать. Но при просмотре действий программы в симуляторе я видел какой-то бред. Программа то тупо пропускала по 3-4 строки,
то оптимизировалась так, что если нужно было поднять в ЛОГ1 Data (DS), а потом (ПОСЛЕ ЭТОГО, ПРЯМ ПОДОЖДАТЬ НАДО) передернуть ReadData (SH_CP), чтобы записать бит в регистр, программа оптимизировала задержку и одновременно поднимала в ЛОГ1 оба вывода, приводя к непредсказуемому значению, записывающемуся в регистр.
Теперь забегая вперед я скажу, что цели я достиг – динамическая индикация работает. Но что не так в приведенном примере я не понимаю. Решение проблемы следующее:
Делаем не один цикл из 16 повторений с 2 байтным числом, а 2 цикла из 8 повторений с двумя однобайтными числами. И все работает как задумано. То есть делаем вот так 2 раза:
Код: Выделить всё
for (i=0; i<8; i++) // 16 раз, для каждого бита повторяем одно и то же
{
if (SRData & 0x80)// Чему равен старший бит, тому будет равен вывод Data (DS)
{
PORT_DATA |= (1<<PinData);
}
else
{
PORT_DATA &= (~(1<<PinData));
}
_delay_us(100); // Чуть ждем хз зачем
PORT_DATA |= (1<<PinReadData); // Запись бита в сдвиговый регистр
_delay_us(100);
PORT_DATA &= (~(1<<PinReadData));
SRData1 <<= 1;// Сдвиг битов влево, чтобы получить следующий бит
}
Только меняем значение SRData при втором прогоне этого цикла и после обоих прогонов добавляем
Код: Выделить всё
PORT_DATA |= (1<<PinShowData); // Вывод данных в порт регистра
_delay_us(100);
PORT_DATA &= (~(1<<PinShowData));Исходники обеих программ я прикладываю.
Суть всего поста в том, что мне необходимо понять компилятор. Можете подсказать, в каком месте я допускаю ошибку? Я на этот бред убил 2 вечера. И на этот пост около часа. Хочется окупить потерянное время приобретенными знаниями. Заранее большое спасибо!
