В предыдущей статье мы разобрали детали из который будет собираться таймер. У устройства есть две кнопки, которые используются для
управления режимами: одна для старта/стопа, вторая для выбора режимов. Цифровой индикатор на три цифры, трехцветный светодиод.
Логика работы таймера исходит из пользовательских функций.
Функции будут следующими:
1. Таймер.
2. Секундомер.
3. Термометр
4. Эмулятор броска двух кубиков.
5. Режим релаксации.
Расмотрим алгоритмы по функциям.
Таймер. При включении начинает обратный отсчет с некоторого начального значения. По умолчанию начальное значение 30 секунд. Но его можно настроить. После окончания отсчета звучит писк. При включении режима таймера, на табло отображается начальное значение. Отсчет начинается по кнопке Старт. Для перехода в режим настройки таймера нужно нажать кнопку Старт и не отпуская ее нажать кнопку Выбор. При этом начинает мигать настраиваемая цифра. Переход по цифрам при помощи кнопки Выбор. Увеличение значения при помощи кнопки Старт.
Секундомер. Просто считает секунды и отображает на табло. Поскольку табло у нас трехзначное, то значит есть возможность посчитать до 999 секунд. или 16 минут и 39 секунд. При входе в режим, отображаются нули, по кнопке Старт начинается отсчет. При повторном нажатии отсчет прекращается. При следующем нажатии начинается отсчет опять с нулевых значений. Для упрощение первая цифра всегда будет обозначать минуту, а две последние секунды, т.е. секундомер может работать до 9 мин 59 секунд.
Температура
Отображается текущая температура, которая считана с датчика. Никаких настроек. Изменение температуры отображаются раз 1/10 секунды.
Кубик
При нажатии на старт случайным образом происходит выброс цифр от 1 до 6 на двух крайних цифрах.
Релаксация.
По очереди цветные светодиоды медленно зажигаются и медленно гаснут.
При нажатии кнопки Выбор происходит смена режима по кругу. Таймер->Секундомер->Температура->Кубики->Релаксация->Таймер
На видео можно посмотреть как это работает реально.
Поскольку памяти в контроллере не слишком много, в STM32F030F4P6 всего 16 килобайт, то используется просто С, не С++ и при этом весь описанный алгоритм уложился в 10 килобайт.
Исходя из таблицы, которая взята из даташита STM32F030F4P6 можно понять какие основные и альтернативные функции можно задействовать на каждой ноге контроллера
Pin number |
Pin name (function after reset) |
Pin type |
IO Structure |
Notes |
Pin functions |
|
TSSOP20 |
Alternate functions |
Additional functions |
||||
2 |
PF0-OSC_IN (PF0) |
I/O |
FT |
I2C1_SDA(5) |
OSC_IN |
|
3 |
PF1-OSC_OUT (PF1) |
I/O |
FT |
I2C1_SCL(5) |
OSC_OUT |
|
4 |
NRST |
I/O |
RST |
Device reset input / internal reset output (active low) |
||
5 |
VDDA |
S |
Analog power supply |
|||
6 |
PA0 |
I/O |
TTa |
USART1_CTS(2), USART2_CTS(3)(5), USART4_TX(5) |
ADC_IN0, RTC_TAMP2, WKUP1 |
|
7 |
PA1 |
I/O |
TTa |
USART1_RTS(2), USART2_RTS(3)(5), EVENTOUT, USART4_RX(5) |
ADC_IN1 |
8 |
PA2 |
I/O |
TTa |
USART1_TX(2), USART2_TX(3)(5), TIM15_CH1(3)(5) |
ADC_IN2 |
|
9 |
PA3 |
I/O |
TTa |
USART1_RX(2), USART2_RX(3)(5), TIM15_CH2(3)(5) |
ADC_IN3 |
|
10 |
PA4 |
I/O |
TTa |
SPI1_NSS, USART1_CK(2) USART2_CK(3)(5), TIM14_CH1, USART6_TX(5) |
ADC_IN4 |
|
11 |
PA5 |
I/O |
TTa |
SPI1_SCK, USART6_RX(5) |
ADC_IN5 |
|
12 |
PA6 |
I/O |
TTa |
SPI1_MISO, TIM3_CH1, TIM1_BKIN, TIM16_CH1, EVENTOUT USART3_CTS(5) |
ADC_IN6 |
|
13 |
PA7 |
I/O |
TTa |
SPI1_MOSI, TIM3_CH2, TIM14_CH1, TIM1_CH1N, TIM17_CH1, EVENTOUT |
ADC_IN7 |
14 |
PB1 |
I/O |
TTa |
TIM3_CH4, TIM14_CH1, TIM1_CH3N, USART3_RTS(5) |
ADC_IN9 |
|
16 |
VDD |
S |
Digital power supply |
17 |
PA9 |
I/O |
FT |
USART1_TX, TIM1_CH2, TIM15_BKIN(3)(5) I2C1_SCL(2)(5) |
- |
|
18 |
PA10 |
I/O |
FT |
USART1_RX, TIM1_CH3, TIM17_BKIN I2C1_SDA(2)(5) |
- |
|
19 |
PA13 (SWDIO) |
I/O |
FT |
(7) |
IR_OUT, SWDIO |
- |
20 |
PA14 (SWCLK) |
I/O |
FT |
(7) |
USART1_TX(2), USART2_TX(3)(5), SWCLK |
- |
1 |
BOOT0 |
I |
B |
Boot memory selection |
||
15 |
VSS |
S |
Ground |
|||
16 |
VDD |
S |
Digital power supply |
1. PC13, PC14 and PC15 are supplied through the power switch. Since the switch only sinks a limited amount of current (3 mA), the use of GPIOs PC13 to PC15 in output mode is limited:
- The speed should not exceed 2 MHz with a maximum load of 30 pF.
- These GPIOs must not be used as current sources (e.g. to drive an LED).
2. This feature is available on STM32F030x6 and STM32F030x4 devices only.
3. This feature is available on STM32F030x8 devices only.
4. For STM32F030x4/6/8 devices only.
5. For STM32F030xC devices only.
6. On LQFP32 package, PB2 and PB8 should be treated as unconnected pins (even when they are not available on the package, they are not forced to a defined level by hardware).
7. After reset, these pins are configured as SWDIO and SWCLK alternate functions, and the internal pull-up on SWDIO pin and internal pull-down on SWCLK pin are activated.
Нам необходимо использовать два GPIO для кнопок, интерфейс SPI, есть свободный SPI1 и интерфейс I2C1, на PA6 будем использовать ШИМ, для этого задействуется TIMER3
Получилось следующее
- boot0
- PF0 OSC_IN
- PF1 OSC_OUT
- NRST - резет
- VDDA - 3.3V
- PA0 -
- PA1 - кнопка 1
- PA2 - кнопка 2
- PA3 -
- PA4 spi nss LE
- PA5 - SPI1_SCK клок
- PA6 - SPI1_MISO не нужен, шим на светодиоды
- PA7 - SPI1_MOSI выход данных
- PB1 - динамик
- VSS - 3.3 v
- VDD - земля
- PA9 - I2C1_SCL(2)(5) -- для опроса температуры
- PA10 - I2C1_SDA(2)(5) -- для опроса температуры
- SWDIO_PA13 - отладчик
- SWCLK_pa14 - отладчик
Нам нужно проводить отображение цифр, логично нагрузить этой задачей один из таймеров, например TIM14, который будет генерировать прерывание и в нем будем менять цифры, если что-то изменилось с последнего момента
Программу условно можно разделить на пользовательскую логику и аппаратный интерфейс. Подробно будем рассматривать воможности аппаратного интерфейса.
Задействованы следующие аппаратные возможности:
1. Подключение внешнего кварца на 8 MГц
2. GPIO - PA1, PA2 - считывание значения кнопок в разных режимах.
3. Отображение значений на трехцифровом дисплее и режимов на трехцветном светодиоде при помощи интерфейса SPI
4. Получение данных от датчика температуры по интерфейсу I2C
5. Использование таймера для выполнения работы по прерываниям (обновление дисплея)
6. Использование таймера для генерации ШИМ
7. Использование GPIO для генерации простейшего звука (без использования АЦП)
Инициализация кварца
// запуск внешнего кварца RCC->CR|=RCC_CR_HSEON; //Запускаем генератор HSE. while (!(RCC->CR & RCC_CR_HSERDY)) {}; // Ждем готовности RCC->CFGR &=~RCC_CFGR_SW; //Сбрасываем биты RCC->CFGR |= RCC_CFGR_SW_HSE; //Переходим на HSE 8Mгц
Отображение будет происходить при помощи драйвера светодиодов, который представляет собой обычный сдвиговый регистр. Соединение по интерфейсу SPI
Инициализация
//Включаем тактирование шины А RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // инициализация периферии GPIO_InitTypeDef GPIO_InitStructure; // a4 для защелки GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_1; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ножка пуш-пул GPIO_Init(GPIOA, &GPIO_InitStructure); SPI_InitTypeDef SPI_InitStructure; // Тактирование модуля SPI1 и порта А RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // Настраиваем ноги SPI1 для работы в режиме альтернативной функции GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_0); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_0); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_5; GPIO_Init(GPIOA, &GPIO_InitStructure); //Заполняем структуру с параметрами SPI модуля SPI_Direction_1Line_Tx; SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //полный дуплекс SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // передаем по 8 бит SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;// SPI_CPOL_Low; // Полярность и SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // фаза тактового сигнала SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // Управлять состоянием сигнала NSS программно SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; // Предделитель SCK SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // Первым отправляется старший бит SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // Режим - мастер SPI_Init(SPI1, &SPI_InitStructure); //Настраиваем SPI1 SPI_Cmd(SPI1, ENABLE); // Включаем модуль SPI1.... // Поскольку сигнал NSS контролируется программно, установим его в единицу // Если сбросить его в ноль, то наш SPI модуль подумает, что // у нас мультимастерная топология и его лишили полномочий мастера. SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set); ..../ замедлитель по частоте void delay_ms(uint32_t ms) { volatile uint32_t nCount; //переменная для счета RCC_ClocksTypeDef RCC_Clocks; //переменная для считывания текущей частоты RCC_GetClocksFreq (&RCC_Clocks); //считываем текущую тактовую частоту nCount=(RCC_Clocks.HCLK_Frequency/10000)*ms; //пересчитываем мс в циклы for (; nCount!=0; nCount--); //гоняем пустые циклы } ....// отправка данных по SPI void DataSend(uint16_t data) { GPIO_SetBits(GPIOA, GPIO_Pin_4); // защелка пока закрыта для приема данных while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); // проверить на готовность GPIO_ResetBits(GPIOA, GPIO_Pin_4); // сбросить защелку, дальше должны идти данные delay_ms(1); SPI_SendData8(SPI1, data);// отправка по 8 бит, для полной засветки нужно отправлять 4 раза while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET) //Передатчик занят? ; // значит ничего не делаем delay_ms(1); GPIO_SetBits(GPIOA, GPIO_Pin_4); //защелка установлена, передача закончена}
Здесь интересно, у нас есть 4 байта, которые мы передаем в сдвиговый регистр, для того чтобы засветить три цифры и многоцветрый светодиод. Передача идет по одному байту (8 бит).
Готовим байты для передачи, каждой цифре на индикаторе соответствуют определенные светодиоды
#define DIG0 0b00111111 #define DIG1 0b00000110 #define DIG2 0b01011011 #define DIG3 0b01001111 #define DIG4 0b01100110 #define DIG5 0b01101101 #define DIG6 0b01111100 #define DIG7 0b00000111 #define DIG8 0b01111111 #define DIG9 0b01100111 #define POINT 0b10000000 #define LED_RED 0b00000100 #define LED_BLUE 0b00000001 #define LED_GREEN 0b00000010 #define MINUS 0b01000000 #define ZERO 0b00000000uint8_t time[3]; // буфер для отображение цифр uint8_t point[3]; // буфер для отображения точек uint8_t point_buff[3]; // предыдущее отображение точек uint8_t time_buff[3]; // предыдущее отображение цифр uint8_t mode_buff=0; // предыдущей режим// массив для упрощения вывода uint8_t digit[]={DIG0,DIG1,DIG2,DIG3,DIG4,DIG5,DIG6,DIG7,DIG8,DIG9,MINUS,ZERO};/* * засветить светодиод в зависимости от режима */void LedShow(){ switch (mode) { case MODE_TIMER: DataSend(LED_BLUE); break; case MODE_STOPWATCH: DataSend(LED_RED); break; case MODE_TEMP: DataSend(0); // выключить break; case MODE_DICE: // кубики DataSend(LED_GREEN); break; /* case MODE_RELAX: break; case MODE_BRIGHT: break; */ default: DataSend(0); }} /* * очистка экрана, но светодиоды оставляем * * */ void Clear(void) { TIM3->CCR1=1050; LedShow(); // засветить светодиод DataSend(0); DataSend(0); DataSend(0); TIM3->CCR1=setbrightness; }/* * проверка на изменение буфера, если изменился, то нужно обновить * если не изменился, то не обновлять чтобы небыло мелькания * * */ uint8_t IsChanged(){ if (mode!=mode_buff){ return 1; } uint8_t i=0; for (i=0;i<3;i++){ if (time[i]!=time_buff[i]){ return 1; } if (point[i]!=point_buff[i]){ return 1; } } return 0;}/* * обновление отображения */ void Refresh(void) { TIM3->CCR1=1050; //delay_ms(10); LedShow(); // послали данные светодиода uint8_t i=0; for (i=0;i<3;i++){ DataSend(digit[time[i]]^point[i]); time_buff[i]=time[i]; point_buff[i]=point[i]; } /* DataSend(digit[time[1]]^point[1]); DataSend(digit[time[2]]^point[2]); time_buff[0]=time[0]; time_buff[1]=time[1]; time_buff[2]=time[2]; */ mode_buff=mode; TIM3->CCR1=setbrightness; } /* * Выключает одну указанную цифру для мелькания при настройке */void Blink(uint8_t Num) { TIM3->CCR1=1050; LedShow(); if (Num == 0){ DataSend(0); DataSend(digit[time[1]]); DataSend(digit[time[2]]); } if (Num == 1){ DataSend(digit[time[0]]); DataSend(0); DataSend(digit[time[2]]); } if (Num == 2){ DataSend(digit[time[0]]); DataSend(digit[time[1]]); DataSend(0); } TIM3->CCR1=setbrightness; }
Отображение данных будет происходить при помощи в прерывании от таймера TIM14
//Таймер 14 у нас висит на шине APB1, включить тактирование RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE); // 14 таймер для обновления экрана TIM_SETUP.TIM_CounterMode = TIM_CounterMode_Up; //считаем вверх TIM_SETUP.TIM_Period = 100; //период таймера 1/10 секунды TIM_SETUP.TIM_Prescaler =8000 ; //TIMER_PRESCALER; //предделитель TIM_TimeBaseInit(TIM14,&TIM_SETUP);//Разрешаем соответствующее прерывание TIM_ITConfig(TIM14, TIM_IT_Update,ENABLE); TIM_Cmd(TIM14, ENABLE); NVIC_EnableIRQ(TIM14_IRQn); __enable_irq(); // разрешены прерывания вообще
Само прерывание от таймера
void TIM14_IRQHandler() { // отображение проверка на изменение и отображение uint8_t _0,_1,_2; uint16_t all; rand(); // сбить генератор псевдослучайных чисел чем чаще вызываем, тем меньше вероятность одинаковых бросков if (mode==MODE_TIMER){ //таймер switch (started){ case MODE1_START: if (alltime==0){ // больше ничего считать не нужно, досчитали до нуля Beep(); time[0]=0; time[1]=0; time[2]=0; if (IsChanged()==1){ // проверка на изменение Refresh(); } started = MODE1_STOP; // останавливаем таймер TIM_ClearITPendingBit(TIM14, TIM_IT_Update); return; } alltime--; all = alltime/10; // разбиваем на каждую цифру _2 = all/100; _1 = (all-(_2*100))/10; _0 = all-(_2*100)-(_1*10); time[0]=_0; time[1]=_1; time[2]=_2; break; case MODE1_STOP: // ничего не делать break; case MODE1_SET: break; } } if (mode==MODE_STOPWATCH) { //секундомер if (alltime==6000){ // 10 минут Beep(); alltime=0; time[0]=0; time[1]=0; time[2]=0; point[2]=POINT; } if (started==0){ // больше ничего считать не нужно TIM_ClearITPendingBit(TIM14, TIM_IT_Update); return; } alltime++; all = alltime/10; /* это одна минута и секунды*/ _2 = all/60; _1 = (all-(_2*60))/10; _0 = all-(_2*60)-(_1*10); point[2]=POINT; // у секундомера точка вторая time[0]=_0; time[1]=_1; time[2]=_2; } if (mode==2){ // температура ReadTemp(); // просто считали температуру } if (mode==3){ // бросок кубика if (started==1) { alltime++; time[0] = rand() % 6 + 1; time[2] = rand() % 6 + 1; if (alltime>30){ started = 0; alltime =0; } } } if (mode==4){ // релакс if (started==1) { if (down==1){ // уменьшаем яркость brigthness=brigthness-10; } else { brigthness=brigthness+10; } if (brigthness<700){ down=0; } if (brigthness>1100){ down=1; switch ( color ) { // меняем цвет case LED_GREEN: color = LED_BLUE; break; case LED_BLUE: color = LED_RED; break; case LED_RED: color = LED_GREEN; break; } DataSend(color); // отправили данные DataSend(0); DataSend(0); DataSend(0); } TIM3->CCR1=brigthness; } TIM_ClearITPendingBit(TIM14, TIM_IT_Update); return; } if (IsChanged()==1){ Refresh(); } TIM_ClearITPendingBit(TIM14,TIM_IT_Update); }
Поскольку мы используем драйвер светодиодов STP16CP05MTR у
которого есть вывод OE, то можем управлять яркостью свечения просто
включая или выключая отображение при помощи шим. Я соединил вывод PA6
контроллера с выводом OE драйвера , активировал на A6 таймер 3 и
подключил на A6
GPIO_InitTypeDef GPIO_InitStructure; // a6 для шим GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//GPIO_Mode_OUT; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ножка пуш-пул GPIO_Init( GPIOA , &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_1); // таймер 3 TIM_SETUP.TIM_CounterMode = TIM_CounterMode_Up; //считаем вверх TIM_SETUP.TIM_Period = 1023; //период таймера 1023 отсчета TIM_SETUP.TIM_Prescaler = 0; //предделитель откл TIM_TimeBaseInit(TIM3, &TIM_SETUP); TIM_OCInitTypeDef PWM_SETUP; //структура настройки ШИМ PWM_SETUP.TIM_Pulse = 0; // начальное значение PWM_SETUP.TIM_OCMode = TIM_OCMode_PWM1; //режим1 center align PWM_SETUP.TIM_OutputState =TIM_OutputState_Enable; //подключаем к выходу PWM_SETUP.TIM_OCPolarity = TIM_OCPolarity_High; //положительная полярность TIM_OC1Init(TIM3, &PWM_SETUP); TIM_Cmd(TIM3, ENABLE); TIM3->CCR1=setbrightness; // значение для ШИМ по умолчанию
Считывание значений при нажатии кнопок, для этого необходимо включить тактирование шины А и инициализировать структуру GPIO
//Включаем тактирование шины А RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // инициализация периферии GPIO_InitTypeDef GPIO_InitStructure; // a1,a2 кнопка GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3; GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_NOPULL; // подтяжка на самой плате GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ножка пуш-пул GPIO_Init(GPIOA, &GPIO_InitStructure);
В самом цикле программы считываем значения и изменяем данные для отображения
/* * started = 0 - на паузе * started = 1 - работает * started = 2 - настройка все на паузу и настраиваем таймер * */#define MODE1_STOP 0 #define MODE1_START 1 #define MODE1_SET 2 /* * * mode = 0 - таймер синий * mode = 1 - секундомер красный * mode = 2 - температура * mode = 3 - кубики зеленый * mode = 4 - релаксация красно-зеленый */#define MODE_TIMER 0 #define MODE_STOPWATCH 1 #define MODE_TEMP 2 #define MODE_DICE 3 #define MODE_RELAX 4 #define MODE_BRIGHT 5 ............... while(1) { if ((GPIOA->IDR & 0x06)==0x06) { // нажаты две кнопки проверка битов if (mode==MODE_TIMER) { if (started!=MODE1_SET){ // включили настройку started=MODE1_SET; timerset=2; alltime = settime*10; // каждую десятую секунду обновление time[0]=settime/100; time[1]=(settime-time[0]*100)/10; time[2]=(settime-time[0]*100-time[1]*10); } else { started=MODE1_STOP; timerset=4; } } if ((GPIOA->IDR & 0x02)==0x02 & ((GPIOA->IDR& 0x04) != 0x04)) { //Кнопка A1 нажата второй бит, A2 не нажата if (!button_flag) { button_flag=1; if (mode==0) { TimerA1(); } else if (mode==1){ if (started==0) { started = 1; alltime =0; point[0] = 0; point[1] = 0; point[2] = 0; point[2]=POINT; } else { started = 0; } } } else if (mode==MODE_DICE){ switch (started) { case MODE1_STOP: started = 1; alltime = 0; break; case MODE1_START: break; case MODE1_SET: setbrightness=setbrightness+50; if (setbrightness>1000){ setbrightness = 700; } TIM3->CCR1=setbrightness; break; } } else if (mode==4){ if (started==0) { started = 1; alltime = 0; } else {started = 0;} } delay_ms(100); } else { button_flag=0; } if((GPIOA->IDR & 0x04) == 0x04 &(GPIOA->IDR & 0x02)!=0x02){ //Кнопка A2 нажата 3-й бит if (!button2_flag){ // переключение по второй кнопке button2_flag = 1; if (mode ==MODE_TIMER) { point[0] = 0; point[1] = 0; point[2] = 0; point[2]=POINT; switch (started){ case MODE1_SET: if (timerset==2){ // настройка первой цифры timerset=1; }else if (timerset==1){ // настройка первой цифры timerset=0; } else if (timerset==0){ timerset=4; started=MODE1_STOP; } break; case MODE1_STOP: mode=MODE_STOPWATCH; started = MODE1_STOP; alltime=0; time[0]=0; time[1]=0; time[2]=0; break; case MODE1_START: mode=MODE_STOPWATCH; started = MODE1_STOP; alltime=0; time[0]=0; time[1]=0; time[2]=0; break; } switch (started){ case MODE1_SET: if (timerset==2){ // настройка первой цифры time[2]++; if (time[2]>9){ time[2]=0; } }else if (timerset==1){ // настройка первой цифры time[1]++; if (time[1]>9){ time[1]=0; } //Refresh(); } else if (timerset==0){ time[0]++; if (time[0]>9){ time[0]=0; } } settime = time[2]*100+time[1]*10+time[0]; Refresh(); break; case MODE1_START: started =MODE1_STOP; alltime=settime; Refresh(); break; case MODE1_STOP: // стартуем, обновление по прерыванию alltime=settime*10; // таймер работает на одну десятую секунды started =MODE1_START; break; } if (IsChanged()==1){ Refresh(); } } else if (mode ==1){ mode=2; } else....
Получение данных по I2C. Адрес LM75A формируется аппаратно, просто замыканием на корпус ножек A0,A1,A2, поскольку у нас других устройств нет, то мы можем себе это позволить.
#define I2CADDR (0b01001000 << 1)uint8_t I2C_Read_Transaction (uint8_t Adress, uint8_t Register, uint8_t *Data, uint8_t Size) { u8 Count=0; // Счётчик успешно принятых байт // Старт I2C_Start_Direction_Adress_Size (I2C_Transmitter, Adress, 1); // Сейчас либо I2C запросит первый байт для отправки, // Либо взлетит NACK-флаг, говорящий о том, что микросхема не отвечает. // Если взлетит NACK-флаг, отправку прекращаем. u8 temp = I2C_BUS->ISR; while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) { if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR = Register; // Отправляю адрес регистра } // Повторный старт I2C_Start_Direction_Adress_Size (I2C_Receiver, Adress, Size); // Принимаем байты до тех пор, пока не взлетит TC-флаг. // Если взлетит NACK-флаг, приём прекращаем. while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) { if (I2C_BUS->ISR & I2C_ISR_RXNE) *(Data+Count++) = I2C_BUS->RXDR; // Принимаю данные } I2C_Stop(); return Count; if (Count == Size) return 1; return 0; }/* * чтение температуры с датчика */ uint16_t ReadTemp(){ uint16_t cou; uint8_t dec; uint8_t buff[2]; buff[0]=0; buff[1]=0; time[0] = 0; time[1] = 0; time[2] = 0; point[0] = 0; point[1] = 0; point[2] = 0; // градусы cou=I2C_Read_Transaction (I2CADDR, 0x00, buff, 2); if (cou!=2){ // не считали return 0; } uint16_t temp; int hi,lo; temp = buff[0]<<8; temp|= buff[1]&0xE0; hi = temp>>8; if (temp<0) { //lo = ((8-(temp>>5))&7); десятые опускаем lo = ((temp>>5)&7); lo=8-lo; dec = hi/10; time[1] = dec; time[0] = hi-dec*10; time[2] = 10; // это минус point[0] = POINT; } else { lo = ((temp>>5)&7); lo*=125; dec = hi/10; time[2] = dec; time[1] = hi-dec*10; time[0] = lo/100; point[1] = POINT; } }
В итоге получилось компактное многофункциональное устройство на STM32, работающее от двух пальчиковых батарей. Исходный код и проект для CoCox можно взять здесь>>>