STM32G031G8U6基于HAL库通过PWM实现红外发射
STM32cubeMX配置
由原理图可知,控制红外发射的管脚为PA5,且低电平导通,高电平关闭。查询STM32G031G8U6数据手册,PA5可以配置为TIM2的PWM CH1输出。
STM32cubeMX时钟配置:
TIM1配置:TIM1用来产生一个us级别的延时,以满足NEC协议的时序。
TIM2-CH1配置:我要产生的是38KHz的PWM,上面时钟配置STM32G031G8U6的时钟为64MHz,因此Counter Period设置为64MHz/38KHz=1684,通过对64Mhz分频得到38KHz。CH polarity设置为low是因为红外发射管是低电平导通,这样我在程序中填的占空比系数是直接更改低电平占空比而不是常规的高电平。至此STM32CubeMX配置完成,生成工程后打开keil工程。
C代码编写
先在TIM2_Init函数中USER CODE BEGIN TIM2_Init 2下添加打开定时器和打开PWM输出的两条语句,如下图中注释这里是重点。
void MX_TIM2_Init(void)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 1684;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
HAL_TIM_Base_Start(&htim2);/* z这里是重点,先开启定时器 */
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1); /* 这里也是是重点,启动PWM输出 */
/* USER CODE END TIM2_Init 2 */
HAL_TIM_MspPostInit(&htim2);
}
NEC协议代码
NEC的协议有很多,自己去查询呀,我把NEC协议全部拆分为一个一个的函数控制这样比较灵活,因为有些遥控器没有按照标准NEC协议来做。
// 发送单个位的红外信号,根据位的值选择发送的持续时间
void IR_LED_SendBit(uint8_t bit)
{
if (bit) {
// 发送逻辑1,持续1.68ms
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,1178);//设置低电平占空比为70%
USER_Delay_us (560);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,0);//设置低电平占空比为0%
USER_Delay_us(1680);
} else {
// 发送逻辑0,持续0.56ms
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,1178);
USER_Delay_us (560);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,0);
USER_Delay_us(560);
}
}
//发送NEC协议开头的引导位,发送9ms 38KHz 的PWM,4.5ms的空闲
void USER_LED_SendStart(void)
{
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,1178);//设置定时器的占空比,最大为1684
USER_Delay_us (9000);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,0);
USER_Delay_us(4500);
}
//发送8bit数据,该函数调用上面的单bit发送函数来实现循环发送,先发最低位。
void USER_LED_SendNEC(uint8_t data)
{
uint8_t i;
// 发送8位数据
for (i = 0; i < 8; i++) //0-7共8bit
{
// 根据数据的当前位判断发送1还是0
IR_LED_SendBit(data & 0x01);
// 将数据右移一位,处理下一位
data >>= 1;
}
}
//微秒级延时函数
void USER_Delay_us(uint16_t nus)
{
__HAL_TIM_SET_COUNTER(DLY_TIM_Handle, 0);
__HAL_TIM_ENABLE(DLY_TIM_Handle);
while (__HAL_TIM_GET_COUNTER(DLY_TIM_Handle) < nus)
{
}
__HAL_TIM_DISABLE(DLY_TIM_Handle);
}
//通过逻辑分析仪抓取我家空调遥控器的的红外发送波形,发现发送的中间需要等待一个间隔时间,发送结束还会发一个bit,所以写了一个等待函数和一个发送结束函数,这可能不是通用的代码。你可以按照自己的需求来更改
void USER_LED_SendWait(void)
{
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,1178);//设置定时器的占空比,最大为1684
USER_Delay_us (560);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,0);
USER_Delay_us(8050);
}
void USER_LED_SendEnd(void)
{
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,1178);//设置定时器的占空比,最大为1684
USER_Delay_us (560);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,0);//设置定时器的占空比,最大为1684
}
//main函数
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
//MX_IWDG_Init();//在STM32CubeMX中开启了IWDG的记得先注释掉,不然一再在复位。
MX_TIM3_Init();
MX_USART2_UART_Init();
MX_TIM2_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)//主要看这里
{
//USER_Led_ctrl(1);
//HAL_Delay(1000);
//IR_LED_SendNEC(0X83, 0x04);
USER_LED_SendStart();//发送起始引导码
USER_LED_SendNEC(0X83);//发送数据
USER_LED_SendNEC(0X06);
USER_LED_SendNEC(0X04);
USER_LED_SendNEC(0X71);
USER_LED_SendNEC(0X00);
USER_LED_SendNEC(0X00);
USER_LED_SendWait();//中间等待
USER_LED_SendNEC(0X80);
USER_LED_SendNEC(0X00);
USER_LED_SendNEC(0X00);
USER_LED_SendNEC(0X00);
USER_LED_SendNEC(0X00);
USER_LED_SendNEC(0X00);
USER_LED_SendNEC(0X00);
USER_LED_SendNEC(0XF5);
USER_LED_SendEnd();//所有数据结束
HAL_Delay(10000);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
调试过程遇到的问题
1.第一次烧录程序,板子出现不受控的运行状态,后来发现是因为在STcube建立工程是使能了IWDG看门狗,在keil中没有将IWDG注释掉,也没有为喂狗,导致一直在超时复位
2.在STcube建立工程后打开keil 编写代码,没有在TIM2的初始化函数中打开TIM2和CH1输出。
3.所有代码调试好,逻辑分析仪抓取输出波形和原来的遥控器是一模一样的,但是就是无法打开空调,从一块不要的遥控器上面拆了一个红外发射LED换上后就可以了。哭了
结果
编译后烧写到硬件上,使用LA5016逻辑分析仪抓取输出波形,成功控制空调开机。
我的波形
原装遥控器的波形
初次发帖,欢迎留言讨论
作者:艾瑞克volvo