STM8S индикация реальной скорости вращения двигателя на модуле TM1638 или самодельный тахометр.
Ссылка на видео в YouTube Эта статья дополнение к статье управление коллекторным двигателем, там мы просто управляли вращением, тут мы будем управлять реальной скоростью которую будем выводить на модуль TM1638.
Ниже как у меня реализован контроль скорости, который состоит из двух основных элементов: оптический прерыватель и диска из бумаги с восемью прорезями.

Общая схема ниже, оптический прерыватель RPI-574 ROHM был изъят из платы старого принтера, вы можете подобрать любой другой, работает прерыватель как транзистор, проходит свет значит открыт, нет закрыт.

Про программу ШИМ управление уже описано в этой статье ссылка, как устроен тахометр читаем ниже.
Принцип работы простой, диск крутится с ротором открывая и закрывая оптический прерыватель с помощью прорезей, остается только сочетать все эти открытия или закрытия за одну секунду, что и будет делать наш контроллер STM8S003F3P6.
Тахометр(счетчик оборотов) реализован следующим образом, вход TIM1, TIM1_CH3(PC3) настроен как Input capture(захват) от которого будет срабатывать прерывание при переходе с низкого к высокому уровню(диск закрывает щель прерывателя), в этом прерывании мы просто прибавляем счетчик срабатывания прерывателя. Сам TIM1 настроен для отсчета одной секунды после отсчета которой должно срабатывать другое прерывание, в котором будет вычисление скорости вращения, запись ее в переменную для отображения и сброс счетчика срабатывания прерывателя. Как вы наверное поняли, показания индикатора обновляется каждую секунду.
Сразу скажу что для подсчета количества срабатывания оптического прерывателя можно было реализовать через интерфейс энкодера который имеется у всех контроллеров STM8, но те ноги на которых можно было это сделать заняты SPI для связи с модулем TM1638.
Тут описание всех настроек TIM1 для работы тахометра, начнем с самого таймера.
TIM1_TimeBaseInit( 1999, TIM1_COUNTERMODE_UP, 1000, 0);
Пред делитель будет 1999, TIM1_COUNTERMODE_UP счет только вверх, считать до 1000, частота тактирования 2MHz, полный счет равен 2000000/((1999+1)*1000)=1 секунда.
Настройка вывода и таймера в режим захвата будет так.
GPIO_Init(GPIOC,GPIO_PIN_3,GPIO_MODE_IN_PU_NO_IT);
TIM1_ICInit( TIM1_CHANNEL_3, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI, TIM1_ICPSC_DIV1, 0);
Настраиваем вывод GPIOC PIN_3 как вход с подтяжкой к питанию, без прерывания. Потом настраиваем TIM1_CHANNEL_3,TIM1_ICPOLARITY_RISING что бы он срабатывал толь при переходе с низкого к высокому уровню, TIM1_ICSELECTION_DIRECTTI прямой ход к IC3, TIM1_ICPSC_DIV1 пред делитель входа равен 1 и фильтр не работает последний нуль.
Осталось сбросить флаги прерываний TIM1_ClearFlag(TIM1_FLAG_UPDATE) и ClearFlag(TIM1_FLAG_CC3), настроить прерывания от переполнения таймера TIM1_ITConfig( TIM1_IT_UPDATE, ENABLE) и TIM1_ITConfig( TIM1_IT_CC3, ENABLE)
от входа TIM1_CHANNEL_3(PC3), TIM1_Cmd(ENABLE) включить таймер и прерывания enableInterrupts().
TIM1_ClearFlag(TIM1_FLAG_UPDATE);
TIM1_ITConfig( TIM1_IT_UPDATE, ENABLE);
TIM1_ClearFlag(TIM1_FLAG_CC3);
TIM1_ITConfig( TIM1_IT_CC3, ENABLE);
TIM1_Cmd(ENABLE);
enableInterrupts();
В обработчиках прерываний INTERRUPT_HANDLER( TIM1_UPD_OVF_TRG_BRK_IRQHandler, 11) и INTERRUPT_HANDLER( TIM1_CAP_COM_IRQHandler, 12) происходит все действие тахометра, в первом вычисляем, сохраняем результат и сбрасываем счетчик количества подсчитанных прорезей, во втором считаем прорези диска.
Как вы видите для работы тахометра нужно не так много усилий и кода, большую часть занимает индикация скорости и управление, ниже я как обычно выложил всю программу.
#include "stm8s.h"
void send_command(uint8_t com);
void send_data(uint8_t *send_data_display);
void display_data(uint32_t multiplier);
uint8_t scan_button(void);
void pause(uint32_t p);
uint8_t result=0;
uint32_t pwm_moto=0;
uint32_t speed_moto=0;
uint32_t speed_moto_ind=0;
//Массив для отправки данных
uint8_t send_data_display[16];
//Массив наших символы для индикации 1,2,3 и так далее
uint8_t symbol[10]={63,6,91,79,102,109,125,7,127,111};
int main( void ){
//Настраиваем порты для SPI
GPIO_Init(GPIOC, GPIO_PIN_5, GPIO_MODE_OUT_OD_HIZ_FAST);
GPIO_Init(GPIOC, GPIO_PIN_4, GPIO_MODE_OUT_OD_HIZ_FAST);
SPI_Init( SPI_FIRSTBIT_LSB, SPI_BAUDRATEPRESCALER_16, SPI_MODE_MASTER, SPI_CLOCKPOLARITY_LOW, SPI_CLOCKPHASE_1EDGE, SPI_DATADIRECTION_1LINE_TX, SPI_NSS_SOFT, 0x07);
SPI_Cmd(ENABLE);
//Настраиваем вывод PA3, TIM2 для генерации ШИМ и включаем
GPIO_Init( GPIOA, GPIO_PIN_3, GPIO_MODE_OUT_OD_HIZ_FAST);
TIM2_TimeBaseInit(TIM2_PRESCALER_1, 100);
TIM2_OC3Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, 0, TIM2_OCPOLARITY_LOW);
TIM2_Cmd(ENABLE);
//Настраиваем TIM1 для отсчета одной секунды
TIM1_TimeBaseInit( 1999, TIM1_COUNTERMODE_UP, 1000, 0);
GPIO_Init(GPIOC,GPIO_PIN_3, GPIO_MODE_IN_PU_NO_IT);
TIM1_ICInit( TIM1_CHANNEL_3, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI, TIM1_ICPSC_DIV1, 0);
TIM1_ClearFlag(TIM1_FLAG_UPDATE);
TIM1_ITConfig( TIM1_IT_UPDATE, ENABLE);
TIM1_ClearFlag(TIM1_FLAG_CC3);
TIM1_ITConfig( TIM1_IT_CC3, ENABLE);
TIM1_Cmd(ENABLE);
enableInterrupts();
while(1){
//Включаем модуль и задаем автоматический инкремент адреса для данных
send_command(140);
send_command(64);
//Отображаем новое значение
display_data(speed_moto_ind);
pause(8000);
//Опрашиваем модуль на нажатие кнопок и присваиваем значение result
result=scan_button();
pause(100);
//Защита от ложных срабатываний, проверяем еще раз нажатие кнопок
if(result!=scan_button()){result=0;}
//Если нажата кнопка, тогда определяем какая и выберем действие
if(result!=0){
//Если кнопка 1 импульс(pwm_moto) равен нулю, значит мотор не работает
if(result==1){pwm_moto=0;}
//Если кнопка 2 то увеличиваем время импульса но не больше 100
if(result==2){if(pwm_moto<100){pwm_moto++;}}
//Если кнопка 3 уменьшаем импульс но не меньше 0
if(result==4){if(pwm_moto>0){pwm_moto--;}}
//Следующие кнопки выбирают значения импульса 20, 40, 60, 80 и 100
if(result==8){pwm_moto=20;}
if(result==16){pwm_moto=40;}
if(result==32){pwm_moto=60;}
if(result==64){pwm_moto=80;}
if(result==128){pwm_moto=100;}
//Устанавливаем новое значение импульса
TIM2_SetCompare3(pwm_moto);
}
}
}
//Этот обработчик будет срабатывать каждую секунду
INTERRUPT_HANDLER(TIM1_UPD_OVF_TRG_BRK_IRQHandler, 11)
{
speed_moto_ind=(speed_moto*125/100);
speed_moto=0;
TIM1_ClearFlag(TIM1_FLAG_UPDATE);
}
//Этот обработчик будет срабатывать на каждой прорези диска
INTERRUPT_HANDLER(TIM1_CAP_COM_IRQHandler, 12)
{
speed_moto++;
TIM1_ClearFlag(TIM1_FLAG_CC3);
}
//Функция для отправки команды
void send_command(uint8_t com){
GPIO_WriteLow(GPIOC, GPIO_PIN_4);
SPI_SendData(com);
while( (SPI->SR & SPI_FLAG_BSY) );
GPIO_WriteHigh(GPIOC, GPIO_PIN_4);
}
//Функция для опроса кнопок
uint8_t scan_button(void){
uint8_t scan_result;
GPIO_WriteLow(GPIOC, GPIO_PIN_4);
SPI_SendData(66);
while( (SPI->SR & SPI_FLAG_BSY) );
//Перенастраиваем SPI на прием данных, принимаем 4байта и сокращаем их до 1байта
SPI_BiDirectionalLineConfig(SPI_DIRECTION_RX);
while( !(SPI->SR & SPI_FLAG_RXNE) );
scan_result=SPI->DR;
while( !(SPI->SR & SPI_FLAG_RXNE) );
scan_result|=(SPI->DR<<1);
while( !(SPI->SR & SPI_FLAG_RXNE) );
scan_result|=(SPI->DR<<2);
while( !(SPI->SR & SPI_FLAG_RXNE) );
scan_result|=(SPI->DR<<3);
GPIO_WriteHigh(GPIOC, GPIO_PIN_4);
//Перенастраиваем SPI на передачу данных
SPI_BiDirectionalLineConfig(SPI_DIRECTION_TX);
return scan_result;
}
//Функция для отправки данных
void send_data(uint8_t *send_data_display){
GPIO_WriteLow(GPIOC, GPIO_PIN_4);
SPI_SendData(192);
while( (SPI->SR & SPI_FLAG_BSY) );
for( uint8_t i=0;i<16;i++){
SPI_SendData(send_data_display[i]);
while( !(SPI->SR & SPI_FLAG_TXE) );
}
while( (SPI->SR & SPI_FLAG_BSY) );
GPIO_WriteHigh(GPIOC, GPIO_PIN_4);
}
//Вычисляем значения и заполняем ими массив для отправки в модуль для отображения
void display_data(uint32_t multiplier){
//Раскомментируйте то что вам нужно для отображения
//send_data_display[0]= symbol[multiplier/10000000];
multiplier= multiplier-(multiplier/10000000*10000000);
//send_data_display[2]= symbol[multiplier/1000000];
multiplier=multiplier-(multiplier/1000000*1000000);
//send_data_display[4]= symbol[multiplier/100000];
multiplier= multiplier-(multiplier/100000*100000);
//send_data_display[6]= symbol[multiplier/10000];
multiplier= multiplier-(multiplier/10000*10000);
//send_data_display[8]= symbol[multiplier/1000];
multiplier= multiplier-(multiplier/1000*1000);
send_data_display[10]= symbol[multiplier/100];
multiplier= (multiplier-multiplier/100*100);
//Тут добовляем точку
send_data_display[12]= symbol[multiplier/10]|128;
multiplier= multiplier-(multiplier/10*10);
send_data_display[14]= symbol[multiplier];
send_data(send_data_display);
}
void pause(uint32_t p){
for(uint32_t i=0; i<p; i++){}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1){}
}
#endif
В заключение, на основе данной программы можно реализовать счетчик чего угодно или немного до думать и реализовать полноценную обратную связь для управления двигателем.