Поставил я себе невыполнимую задачу написать первую свою программку для ардуины. Взор упал на датчик DHT11. За три дня удалось наладить общение с датчиком без библиотеки. Обрадовался я и поставил себе еще более сложную задачу для новичка: наладить связь 4-разрядным дисплеем, где в качестве драйвера микросхема TM1637. Покурил DATASHEET, наваял за недельку в свободное время код. Код (C++): byte command1 = B01000000; //Команда записи данных в регистр дисплея byte command2 = B11000000; //Команда "начальный адрес дисплея" byte symbol_1 = 0xF0;//Символ_1 byte symbol_2 = 0x20;//Символ_2 byte symbol_3 = 0xF0;//Символ_3 byte symbol_4 = 0x80;//Символ_4 byte symbol_5 = 0xF0;//Символ_5 byte symbol_6 = 0xD0;//Символ_6 byte command3 = B10001101; //Яркость и ON/OFF дисплея byte command4 = B01000010;//Команда чтения клавиатуры. unsigned int bit_n; unsigned int WAIT_HL = 0; unsigned int pack = 0x01; unsigned int send_byte; //int SCL_LOW = 0; //Переменная, в которой хранится номер цикла при ожидании LOW на SCL. //int SCL_HIHG = 0; //Переменная, в которой хранится номер цикла во время ожидания HIGH SCL. unsigned int SCL_TM1637 = B00001000; //Задаем 3 пин, как выход SCL unsigned int SDA_TM1637 = B00000100; //Задаем 2 пин, как шину SDA volatile boolean SCL_STAT = true;//Переменная, в которой хранится текущее состояние выхода SCL volatile boolean ACK_tm1637 = false; //Переменная в которой возвращается проверка сигнала ACK (подтверждение приема байта) void setup() { TIMSK2 &= ~(1<<TOIE2);// Запрещаем прерывания, сбросив бит TOIE2 в регистре TIMSK2. TCCR2A &= ~((1<<COM2A1) | (1<<COM2A0) | (1<<COM2B1) | (1<<COM2B0) | (1<<WGM21) | (1<<WGM20));//Сбрасываем биты 7 - 4 в регистре TCCR2A, так как переключение выходов OC2A и OC2B нас пока не интересует, а также отключаем режим ШИМ (WGM). TIMSK2 |= (1<<TOIE2);// Разрешаем прерывания, установив бит TOIE2 в регистре TIMSK2. Прерывание будет переворачивать сигнал на SCL DDRD |= B00001100; //Инициализируем третий и второй пины порта D как выходы. PORTD |= B00001100; //Выводим HIGH на третий и второй пины порта D. Serial.begin(9600); } void generator_SCL(){ PORTD = PORTD ^ SCL_TM1637; //Инвертируем третий пин порта D. Тем самым формируем сигнал SCL. //Serial.println(PORTD); SCL_STAT = !SCL_STAT; //Записываем состояние SCL в переменную SCL_STAT } void start_transfer(){ DDRD |= B00001100; //Инициализируем третий и второй пины порта D как выходы. PORTD |= B00001100; //Выводим HIGH на SCL и SDA SCL_STAT = true; delay(1); PORTD &= B11111011; // Посылаем на SDA LOW, тем самым отправляем приемнику сигнал о старте передачи данных (SCL = HIGH). TCCR2B = 0x02; //Подключаем 2 Мгц к Таймеру2. Таймер начинает считать до 255, вызывать подпрограмму прерывания (generator_SCL()) и сбрасываться в 0. } void stop_start(){ //Используется после инициализации и перед третьей командой WAIT_HL = 0; while(WAIT_HL < 2000){ //Ждем LOW на SCL (см. DATASHEET TM1637 if(!(PIND & B00001000)){ //Если пришел LOW, //Serial.println("GOOD"); WAIT_HL = 2000; //идем дальше } WAIT_HL += 1; } WAIT_HL = 0; PORTD |= SDA_TM1637; //выводим 1 на пин SDA, while(WAIT_HL < 50){ WAIT_HL += 1; } WAIT_HL = 0; PORTD &= B11111011; //выводим 0 на пин SDA, } void end_transfer(){ TCCR2B = 0x00; //Останавливаем Таймер2. PORTD |= B00001000; //Выводим на SCL HIGH SCL_STAT = true; // Не забываем присвоить переменной статуса SCL значение true DDRD |= B00000100; //Инициализируем второй пин порта D (SDA) как выход. PORTD &= B11111011; //Выводим на SDA LOW. delay(1); TCNT2 = 0; //Обнуляем таймер. PORTD |= B00000100; //Выводим на SDA HIGH. } void wait_ack(){ ACK_tm1637 = 0; WAIT_HL = 0; while(WAIT_HL < 2000){ //Ждем LOW на SCL (см. DATASHEET TM1637 if(!SCL_STAT){ //Если пришел LOW, //Serial.println("GOOD"); WAIT_HL = 2000; //идем дальше } WAIT_HL += 1; } DDRD &= B11111011; //Инициализируем пин SDA как вход PORTD |= B00000100; //Подключаем подтягивающий резистор к SDA WAIT_HL = 0; while(WAIT_HL < 2000){ //Начинаем слушать шину SDA не более 2000 циклов //Serial.println(PORTD); if(!(PIND & B00000100)){//Если SDA = LOW, Serial.println("OK"); Serial.println(pack); WAIT_HL = 2000; ACK_tm1637 = 1; //присваиваем переменной ACK_tm1637 значение 1, как подтверждение получения ACK } WAIT_HL += 1; } if(ACK_tm1637 == 0){ Serial.println(pack); Serial.println("NO ACK!"); } WAIT_HL = 0; while(WAIT_HL < 2000){ //Ждем положительного фронта на SCL (см. DATASHEET TM1637 if(PIND & B00001000){ //Если пришел положительный фронт, //Serial.println("GOOD"); WAIT_HL = 2000; //уходим в loop } WAIT_HL += 1; } WAIT_HL = 0; DDRD |= B00000100; //Инициализируем второй пин (SDA) порта D как выход. } void byte_send(){ send_byte = pack; //DDRD |= B00000100; //Инициализируем второй пин (SDA) порта D как выход. bit_n = 0; while(bit_n <= 7){ //отправляем байт на SDA //Serial.println(pack); //Serial.println(bit_n); if(!SCL_STAT){ //Если пин SCL переключился на LOW //Serial.println(pack); if(send_byte & B00000001){ //Если младший бит равен 1, //Serial.println("1"); PORTD |= SDA_TM1637; //выводим 1 на пин SDA, send_byte = send_byte >> 1; //сдвигаем байт на 1 бит вправо и переходим к отправке следующего бита. bit_n += 1; //i += 1; while(!SCL_STAT){} //Ждем HIGH на SCL } else{ //Если младший бит равен 0 //Serial.println("0"); PORTD &= ~SDA_TM1637; //выводим на SDA 0, send_byte = send_byte >> 1; //сдвигаем байт вправо на 1 бит и переходим к отправке следующего бита bit_n += 1; // z +=1; while(!SCL_STAT){} //Ждем HIGH на SCL } } } //Serial.println(pack); //wait_ack(); //После передачи байта переходим в подпрограмму ожидания сигнала подтверждения приема } void loop() { //delay(1000); //Serial.println(pack); start_transfer(); pack = command1; byte_send(); wait_ack(); stop_start(); pack = command2; byte_send(); wait_ack(); pack = byte(random()); byte_send(); wait_ack(); pack = byte(random()); byte_send(); wait_ack(); pack = byte(random()); byte_send(); wait_ack(); pack = byte(random()); byte_send(); wait_ack(); //pack = symbol_5; //byte_send(); //wait_ack(); //pack = symbol_6; //byte_send(); //wait_ack(); stop_start(); pack = command3; byte_send(); wait_ack(); pack = command4; byte_send(); wait_ack(); end_transfer(); delay(5000); } ISR(TIMER2_OVF_vect) { generator_SCL(); } А работать он не хочет. Хотя сигнал ACK от TM1637 приходит. Похоже, я не совсем разобрался в структуре команд данной микросхемы. Замечена следующая странность. Если залить в Ардуино простейший скетч с библиотекой для работы с TM1637, подождать вывода на экран каких-нибудь цифр, а потом залить мой код, то с каждой отправкой пакетов на дисплее начинают менять кракозябры. Причем, такое происходит, даже если отправлять постоянные, а не рандомные значения. Тоесть, в память TM1637 что-то записывается , но что и по какой логике - не понятно. Если сделать рестарт Ардуины, то на дисплее не светится ни один сегмент. Не совсем понятен следующий момент из даташита. Что значит инициализация? Правильно ли я понял, что это команда 0100 0000? В блок-схеме получается три команды: инициализация, команда записи в память, начальный адрес. А на графике две команды.
Ну вот из библиотеки, например, можно удалить много ненужного для простой задачи кода. Вот, что получилось. Этого достаточно, чтобы отображать какие либо символы. Зажигать по отдельности любой сегмент: Код (C++): #define SCL_TM1637 3 //Задаем 3 пин, как шину SCL #define SDA_TM1637 2 //Задаем 2 пин, как шину SDA int pack; byte command1 = B01000000; //Команда записи данных в регистр дисплея byte command2 = B11000000; //Команда "начальный адрес дисплея" byte symbol_1 = 0x76;//Символ_1 byte symbol_2 = 0x79;//Символ_2 byte symbol_3 = 0x38;//Символ_3 byte symbol_4 = 0x73;//Символ_4 byte command3 = B10001111; //Яркость и ON/OFF дисплея void setup() {} void TM1637_start()// просто функция "старт" для протокола I2C { PORTD |= 1<<SCL_TM1637; PORTD |= 1<<SDA_TM1637; _delay_us(2); PORTD &= ~(1<<SDA_TM1637); } void TM1637_stop() // просто функция "стоп" для протокола I2C { PORTD &= ~(1<<SCL_TM1637); _delay_us(2); PORTD &= ~(1<<SDA_TM1637); _delay_us(2); PORTD |= 1<<SCL_TM1637;; _delay_us(2); PORTD |= 1<<SDA_TM1637; } void TM1637_writeByte(int8_t pack){ // служебная функция записи данных по протоколу I2C, с подтверждением (ACK) uint8_t i; for(i=0;i<8;i++) { PORTD &= ~(1<<SCL_TM1637); if(pack & 0x01) { PORTD |= 1<<SDA_TM1637;} else {PORTD &= ~(1<<SDA_TM1637);} _delay_us(3); pack = pack>>1; PORTD |= 1<<SCL_TM1637; _delay_us(3); } PORTD &= ~(1<<SCL_TM1637); _delay_us(5); DDRD &= ~(1<<SDA_TM1637);// если поменяете порт на какой-то другой кроме PORTD, то тут тоже все DDRD на другие DDRx менять надо будет while((PIND & (1<<SDA_TM1637))); DDRD |= (1<<SDA_TM1637); PORTD |= 1<<SCL_TM1637; _delay_us(2); PORTD &= ~(1<<SCL_TM1637); } void TM1637_init()// инициализирует переменные, назначает порт и пины для связи с дисплеем, а потом чистит дисплей { DDRD |= (1<<SCL_TM1637) | (1<<SDA_TM1637); } void TM1637_send(){ TM1637_init(); //Инициализируем пины SCL и SDA как выходы TM1637_start(); pack = command1; //Команда записи в регистр дисплея TM1637_writeByte(pack); TM1637_stop(); TM1637_start(); pack = command2; //Команда начального адреса для автоинкремента адреса TM1637_writeByte(pack); pack = symbol_1; //Символ 1 TM1637_writeByte(pack); pack = symbol_2; //Символ 2 TM1637_writeByte(pack); pack = symbol_3; //Символ 3 TM1637_writeByte(pack); pack = symbol_4; //Символ 4 TM1637_writeByte(pack); TM1637_stop(); TM1637_start(); pack = command3; //Команда, задающая яркость дисплея TM1637_writeByte(pack); TM1637_stop(); delay(2000); } void loop() { //Свой код (symbol_1 = , symbol_2 =, symbol_3 =, symbol_4 = ...) TM1637_send(); } Код взят отсюда http://xn--90azbaece.xn--p1ai/?p=97 Для удобства можно еще оставить таблицу-генератор символов.
а десятичной точкой научились управлять? А то на дисплее она есть, а ни в одной библиотеке про нее ни строчки, только двоеточие функционирует.
Ей можно управлять только там, где она есть) В моем шилде в десятичных точках попросту нет светодиодов) В библиотеке как раз есть управление точкой. Смотрите внимательно по ссылке, которую я выше привел. А, она неправильно вставилась. Исправляюсь http://xn--90azbaece.xn--p1ai/?p=97 Наличие точки можно проверить, записав в какой-либо из сегментов B10000000, если я все правильно понял. Но если у вас шилд с работающим двоеточием это уже говорит об отсутствии десятичных точек, по-моему.
Ну почему же "ни в одной ни строчки"?. Есть, например в TM1637Display (https://github.com/avishorp/TM1637/) Код (C++): @param num The number to be shown //! @param dots Dot/Colon enable. The argument is a bitmask, with each bit corresponding to a dot //! between the digits (or colon mark, as implemented by each module). i.e. //! For displays with dots between each digit: //! * 0.000 (0b10000000) //! * 00.00 (0b01000000) //! * 000.0 (0b00100000) //! * 0.0.0.0 (0b11100000) //! For displays with just a colon: //! * 00:00 (0b01000000) //! For displays with dots and colons colon: //! * 0.0:0.0 (0b11100000) //! @param leading_zero When true, leading zeros are displayed. Otherwise unnecessary digits are //! blank. NOTE: leading zero is not supported with negative numbers. //! @param length The number of digits to set. The user must ensure that the number to be shown //! fits to the number of digits requested (for example, if two digits are to be displayed, //! the number must be between 0 to 99) //! @param pos The position of the most significant digit (0 - leftmost, 3 - rightmost) void showNumberDecEx(int num, uint8_t dots = 0, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0);
Ну, да нет идеальных библиотек, увы. Но прежде чем писать свою библиотеку с нуля, может стоит поучиться на примерах других Взять существующую библиотеку, разобраться как она утсроена. А потом убрать,то что считаете лишним, и добавить то, что считаете нужным. Ваши эксперименты дело хорошее, но до библиотке пока еще как до луны. Вот захочется мне, например пины 10 и 11 использовать? И что?