【DigiKey创意大赛】基于STM32H7B3I-DK的智能家居助手
[复制链接]
基于STM32H7B3I-DK的智能家居助手
作者:qinyunti
汇总内容附件
- 准备
基于MDK5.38其安装不再赘述。
下载软件包en.stm32cubeh7_v1-11-0
https://www.st.com/zh/embedded-software/stm32cubeh7.html
解压en.stm32cubeh7_v1-11-0.zip打开STM32Cube_FW_H7_V1.11.0\Projects\STM32H7B3I-DK\Applications\FreeRTOS\FreeRTOS_ThreadCreation\MDK-ARM\Project.uvprojx
安装STM32H7xx_DFP.3.1.1
即可编译下载Demo工程
- 整体设计
- 开发板可以采集红外模块接收信号,学习记录遥控器的按键信息。
- 可以控制输出任意波形的红外信号,模拟任意遥控器。
- 采集ADC音频供语音识别之用。语音识别控制红外发射对应按键。
- 可以通过串口连接蓝牙WIFI模块或者直连电脑。或者直连蓝牙/WIFI模块与手机通讯。
- 手机和电脑可以语音识别直接发送红外按键信息给开发板,通过红外模块转发。
- 手机和电脑可以和开发板通讯交互获取信息,控制下发。
三.38KHz红外载波信号产生与PWM极限测试
原理
使用PWM输出38KHz的信号。
引脚
原理图看到可以使用PH10进行测试,对应TIM5_CH1
驱动代码
驱动实现pwm.c
#include "main.h"
#define TIMx TIM5
#define CLK 280000000ul
/* Timer handler declaration */
TIM_HandleTypeDef TimHandle;
/* Timer Output Compare Configuration Structure declaration */
TIM_OC_InitTypeDef sConfig = {0};
/* Counter Prescaler value */
uint32_t uhPrescalerValue = 0;
void pwm_init(uint32_t freq)
{
GPIO_InitTypeDef GPIO_InitStruct;
uhPrescalerValue = (uint32_t)(SystemCoreClock / CLK) - 1;
__HAL_RCC_TIM5_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
/* PH10 TIM5 CH1
*/
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM5;
GPIO_InitStruct.Pin = GPIO_PIN_10;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
/* TIM5
*/
TimHandle.Instance = TIMx;
TimHandle.Init.Prescaler = uhPrescalerValue;
TimHandle.Init.Period = (CLK/freq) - 1;
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0;
if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK)
{
/* Initialization Error */
Error_Handler();
}
/*##-2- Configure the PWM channels #########################################*/
/* Common configuration for all channels */
sConfig.OCMode = TIM_OCMODE_PWM1;
sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfig.OCFastMode = TIM_OCFAST_DISABLE;
sConfig.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;
sConfig.OCIdleState = TIM_OCIDLESTATE_RESET;
/* Set the pulse value for channel 1 */
sConfig.Pulse = TimHandle.Init.Period/2;
if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TIM_CHANNEL_1) != HAL_OK)
{
/* Configuration Error */
Error_Handler();
}
/* Start channel 1 */
if (HAL_TIM_PWM_Start(&TimHandle, TIM_CHANNEL_1) != HAL_OK)
{
/* PWM Generation Error */
Error_Handler();
}
}
pwm.h
#ifndef PWM_H
#define PWM_H
#ifdef __cplusplus
extern "C" {
#endif
void pwm_init(uint32_t freq);
#ifdef __cplusplus
}
#endif
#endif
38KHz测试
极限测试
x-y表示直接设置脉宽寄存器为x,周期寄存器为y,从最小值开始尝试,测试最大可输出PWM频率。 APB2最大时钟是280MHz,理论最大输出就是该频率。
0-0,始终为低
1-0 输出可以看到频率是309M左右,但是可能信号驱动能力,杜邦线接线和示波器太低端所以测试不出来,但是可以看到有频率出来了 应该就是280/1M。
0-1,始终为低
1-1 280/2M,波形出不来但是可以看出频率基本正确135MHZ≈140MHz。
1-2 280/3M 波形出不来但是可以看出频率基本正确≈140MHz。
2-2 波形出不来但是可以看出频率基本正确≈93MHz。
10M 直接驱动接口设置10MHz pwm_init(10000000); 准确。
20M 直接驱动接口设置20MHz pwm_init(20000000); 频率准确,波形出不来。
40M 直接驱动接口设置40MHz pwm_init(40000000); 频率准确,波形出不来。
四.任意波形调制信号产生
原理
定义输出状态数据结构,即对应的输出状态和保持时间
typedef struct iostate
{
uint8_t level;
uint32_t holdtime;
}iostate_t;
定义对应缓冲区,如下定义了2个序列,每个序列有40个状态,以0xFF状态作为结束标志。
iostate_t s_iostate[2][40]=
{
{
{0,3000}, /* 0 - 3mS 1000 25uS*/
{1,3000}, /* 1 - 3ms */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
...
{0Xff,0}
},
{
{0,3000}, /* 0 - 3mS 1000 25uS*/
{1,3000}, /* 1 - 3ms */
{0,500}, /* 0- 500uS */
...
{0Xff,0}
}
};
查询输出缓冲区,输出第一个引脚状态,并设置定时器周期为对应的保持时间。在定时器回调中,查询下一状态需要保持的时间,设置对应的周期,并设置对应引脚状态。以上直到输出所有的状态。
引脚
使用引脚PI10输出
驱动代码
Ir.h
#ifndef IR_H
#define IR_H
#ifdef __cplusplus
extern "C" {
#endif
void ir_init(void);
void ir_send(uint8_t num);
uint8_t ir_isdone(void);
#ifdef __cplusplus
}
#endif
#endif
Ir.c
#include <stdint.h>
#include "main.h"
#define TIMx TIM7
#define CLK 1000000ul
#define ir_outh() HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_SET)
#define ir_outl() HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_RESET)
static void ir_handle(void);
static TIM_HandleTypeDef TimHandle;
void time_init(uint32_t period)
{
RCC_ClkInitTypeDef clkconfig;
uint32_t uwTimclock, uwAPB1Prescaler;
uint32_t uwPrescalerValue;
uint32_t pFLatency;
__HAL_RCC_TIM7_FORCE_RESET();
__HAL_RCC_TIM7_RELEASE_RESET();
__HAL_RCC_TIM7_CLK_DISABLE();
/*Configure the TIMx IRQ priority */
HAL_NVIC_SetPriority(TIM7_IRQn, 5 ,0U);
/* Enable the TIMx global Interrupt */
HAL_NVIC_EnableIRQ(TIM7_IRQn);
/* Enable TIM7 clock */
__HAL_RCC_TIM7_CLK_ENABLE();
/* Get clock configuration */
HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);
/* Get APB1 prescaler */
uwAPB1Prescaler = clkconfig.APB1CLKDivider;
/* Compute TIM7 clock */
if (uwAPB1Prescaler == RCC_HCLK_DIV1)
{
uwTimclock = HAL_RCC_GetPCLK1Freq();
}
else
{
uwTimclock = 2UL * HAL_RCC_GetPCLK1Freq();
}
/* Compute the prescaler value to have TIM7 counter clock equal to 1MHz */
uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U);
/* Initialize TIM7 */
TimHandle.Instance = TIMx;
/* Initialize TIMx peripheral as follow:
+ Period = [(TIM7CLK/1000) - 1]. to have a (1/1000) s time base.
+ Prescaler = (uwTimclock/1000000 - 1) to have a 1MHz counter clock.
+ ClockDivision = 0
+ Counter direction = Up
*/
TimHandle.Init.Period = period - 1U;
TimHandle.Init.Prescaler = uwPrescalerValue;
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0;
TimHandle.Init.AutoReloadPreload = 0;
__HAL_TIM_URS_ENABLE(&TimHandle);
if(HAL_TIM_Base_Init(&TimHandle) == HAL_OK)
{
/* Start the TIM time Base generation in interrupt mode */
HAL_TIM_Base_Start_IT(&TimHandle);
}
}
void TIM7_IRQHandler(void)
{
//static uint32_t freq=1000;
ir_handle();
//time_init(freq+=1000);
if (__HAL_TIM_GET_FLAG(&TimHandle, TIM_FLAG_UPDATE) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(&TimHandle, TIM_IT_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_IT(&TimHandle, TIM_IT_UPDATE);
}
}
///HAL_GPIO_TogglePin(GPIOI, GPIO_PIN_10);
}
void time_stop()
{
HAL_TIM_Base_Stop_IT(&TimHandle);
}
void ir_init(void)
{
GPIO_InitTypeDef gpio_init_structure;
__HAL_RCC_GPIOI_CLK_ENABLE();
/* Configure the GPIO_LED pin */
gpio_init_structure.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_structure.Pull = GPIO_PULLUP;
gpio_init_structure.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_structure.Pin = GPIO_PIN_10;
HAL_GPIO_Init(GPIOI, &gpio_init_structure);
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10, GPIO_PIN_RESET);
}
typedef struct iostate
{
uint8_t level;
uint32_t holdtime;
}iostate_t;
iostate_t s_iostate[2][40]=
{
{
{0,3000}, /* 0 - 3mS 1000 25uS*/
{1,3000}, /* 1 - 3ms */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2,5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,4000}, /* 1 -4mS */
{0,500}, /* 0- 500uS */
{1,60000}, /* 1 -1000mS */
{0Xff,0}
},
{
{0,3000}, /* 0 - 3mS 1000 25uS*/
{1,3000}, /* 1 - 3ms */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2,5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,4000}, /* 1 -4mS */
{0,500}, /* 0- 500uS */
{1,22500}, /* 1 -22.5mS */
{0Xff,0}
}
};
static uint32_t s_ir_index = 0;
static uint32_t s_ir_done = 1;
static uint32_t s_ir_num = 0;
static void ir_handle(void)
{
if(s_ir_index < sizeof(s_iostate[0])/sizeof(s_iostate[0][0]))
{
if(s_iostate[s_ir_num][s_ir_index].level != 0xFF)
{
if(s_iostate[s_ir_num][s_ir_index].level == 0)
{
ir_outl();
}
else
{
ir_outh();
}
time_init(s_iostate[s_ir_num][s_ir_index].holdtime);
}
else
{
time_stop();
s_ir_done = 1;
}
}
else
{
s_ir_done = 1;
time_stop();
}
s_ir_index++;
}
void ir_send(uint8_t num)
{
s_ir_index = 0;
s_ir_done = 0;
s_ir_num = num;
if(s_iostate[s_ir_num][s_ir_index].level == 0)
{
ir_outl();
}
else
{
ir_outh();
}
time_init(s_iostate[s_ir_num][s_ir_index].holdtime);
s_ir_index++;
}
uint8_t ir_isdone(void)
{
return s_ir_done;
}
任意波形输出测试
测试可以看到和缓冲区数据定义完全一致
{
{0,3000}, /* 0 - 3mS 1000 25uS*/
{1,3000}, /* 1 - 3ms */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2,5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,4000}, /* 1 -4mS */
{0,500}, /* 0- 500uS */
{1,60000}, /* 1 -1000mS */
{0Xff,0}
},
注意事项
配置URS为1
__HAL_TIM_URS_ENABLE(&TimHandle);
这样只有溢出时才会产生中断,否则如下初始化时会手动产生一次更新事件而进入中断。与我们需要的到了周期再中断需求不符。
- 红外模块设计
原理
原理图如下
接收部分U4反接,在反向电压一定范围内接收到指定红外光时表现为恒流源,通过R6,R7转为电压输出,并分压控制Q6导通通过R9输出低。无红外接收时Q6不导通输出高。
输出部分是5脚输出38KHz载波信号,4脚输出调制信号,两者的与的关系,控制U3上按照调制信号输出或者不输出38KHz载波。
PCB设计
焊接并测试
20块钱打样回来后焊接
供电,使用普通要控制发射信号,使用示波器测量1脚是否有对应波形。
发送等完成2和3的驱动后测试。
信号驱动测试
按照2和3的驱动代码产生载波和调制信号,测量发送和接收到的波形
黄色是发送,紫色是接收。可以看到收发正常。
- 串口通讯与驱动
为了方便与其他外设和上位机交互,先实现灵活好用的串口驱动接口。
原理
设计环形缓冲区。接收中断将数据写u人缓冲区,读API查询缓冲区。
环形缓冲区读写需要做临界段保护处理。
代码
bsp_uart.h
#ifndef BSP_UART_H
#define BSP_UART_H
#ifdef __cplusplus
extern "C" {
#endif
void bsp_uart_init(void);
#ifdef __cplusplus
}
#endif
#endif
Bsp_uart.c
#include <stdio.h>
#include "main.h"
#define USARTx USART1
#define USARTx_CLK_ENABLE() __USART1_CLK_ENABLE()
#define USARTx_RX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
#define USARTx_TX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
#define USARTx_FORCE_RESET() __USART1_FORCE_RESET()
#define USARTx_RELEASE_RESET() __USART1_RELEASE_RESET()
/* Definition for USARTx Pins */
#define USARTx_TX_PIN GPIO_PIN_9
#define USARTx_TX_GPIO_PORT GPIOA
#define USARTx_TX_AF GPIO_AF7_USART1
#define USARTx_RX_PIN GPIO_PIN_10
#define USARTx_RX_GPIO_PORT GPIOA
#define USARTx_RX_AF GPIO_AF7_USART1
UART_HandleTypeDef UartHandle;
void bsp_uart_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
/*##-1- Enable peripherals and GPIO Clocks #################################*/
/* Enable GPIO TX/RX clock */
USARTx_TX_GPIO_CLK_ENABLE();
USARTx_RX_GPIO_CLK_ENABLE();
/* Select SysClk as source of USART1 clocks */
RCC_PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
RCC_PeriphClkInit.Usart16ClockSelection = RCC_USART16CLKSOURCE_D2PCLK2;
HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphClkInit);
/* Enable USARTx clock */
USARTx_CLK_ENABLE();
/*##-2- Configure peripheral GPIO ##########################################*/
/* UART TX GPIO pin configuration */
GPIO_InitStruct.Pin = USARTx_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = USARTx_TX_AF;
HAL_GPIO_Init(USARTx_TX_GPIO_PORT, &GPIO_InitStruct);
/* UART RX GPIO pin configuration */
GPIO_InitStruct.Pin = USARTx_RX_PIN;
GPIO_InitStruct.Alternate = USARTx_RX_AF;
HAL_GPIO_Init(USARTx_RX_GPIO_PORT, &GPIO_InitStruct);
/*##-1- Configure the UART peripheral ######################################*/
/* Put the USART peripheral in the Asynchronous mode (UART Mode) */
/* UART configured as follows:
- Word Length = 8 Bits (7 data bit + 1 parity bit) :
BE CAREFUL : Program 7 data bits + 1 parity bit in PC HyperTerminal
- Stop Bit = One Stop bit
- Parity = ODD parity
- BaudRate = 9600 baud
- Hardware flow control disabled (RTS and CTS signals) */
UartHandle.Instance = USARTx;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if(HAL_UART_Init(&UartHandle) != HAL_OK)
{
/* Initialization Error */
Error_Handler();
}
__HAL_UART_ENABLE_IT(&UartHandle,UART_IT_RXNE);
HAL_NVIC_SetPriority(USART1_IRQn, 5 ,0U);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
int fputc(int ch, FILE *f)
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART1 and Loop until the end of transmission */
HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
void uart_sendbyte(uint8_t val)
{
HAL_UART_Transmit(&UartHandle, (uint8_t *)&val, 1, 0xFFFF);
}
extern void uart_api_rx_handler(const uint8_t *buffer, uint32_t length);
void USART1_IRQHandler(void)
{
uint8_t ch;
ch = (uint16_t) READ_REG(UartHandle.Instance->RDR);
uart_api_rx_handler(&ch, 1);
}
Uart_api.c
#include "uart_api.h"
extern void uart_sendbyte(uint8_t val);
typedef struct
{
uint32_t datalen_u32;
uint32_t maxlen_u32;
uint32_t in_u32;
uint32_t out_u32;
uint8_t* buffer_pu8;
}ring_buffer_t;
static uint8_t uart_ring_buffer[UART_RING_BUFFER_SIZE];
static ring_buffer_t s_ring_buffer_t=
{
.datalen_u32 = 0,
.maxlen_u32 = sizeof(uart_ring_buffer),
.in_u32 = 0,
.out_u32 = 0,
.buffer_pu8 = uart_ring_buffer,
};
void uart_api_rx_handler(const uint8_t *buffer, uint32_t length)
{
uint32_t i;
for(i=0;i<length; i++)
{
if(s_ring_buffer_t.datalen_u32 < s_ring_buffer_t.maxlen_u32)
{
s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.in_u32] = buffer;
s_ring_buffer_t.datalen_u32++;
s_ring_buffer_t.in_u32++;
s_ring_buffer_t.in_u32 %= s_ring_buffer_t.maxlen_u32;
}
else
{
/* full */
break;
}
}
}
uint32_t uart_api_read(uint8_t *buff, uint32_t len)
{
uint32_t readlen = 0;
if(s_ring_buffer_t.datalen_u32 == 0)
{
return 0;
}
Alloc_Critical();
Enter_Critical();
uint32_t i;
for(i=0;i<len;i++)
{
if(s_ring_buffer_t.datalen_u32 > 0)
{
buff = s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.out_u32];
s_ring_buffer_t.datalen_u32--;
s_ring_buffer_t.out_u32++;
s_ring_buffer_t.out_u32 %= s_ring_buffer_t.maxlen_u32;
readlen++;
}
else
{
break;
}
}
Exit_Critical();
return readlen;
}
void uart_api_write(uint8_t *buff, uint32_t len)
{
uint32_t i;
for(i=0; i<len ;i++)
{
uart_sendbyte(buff);
}
}
Uart_api.h
#ifndef UART_API_H
#define UART_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "cmsis_os.h"
#define Alloc_Critical()
#define Enter_Critical() taskENTER_CRITICAL();
#define Exit_Critical() taskEXIT_CRITICAL();
#define UART_RING_BUFFER_SIZE (1024+64)
void uart_api_rx_handler(const uint8_t *buffer, uint32_t length);
uint32_t uart_api_read(uint8_t *buff, uint32_t len);
void uart_api_write(uint8_t *buff, uint32_t len);
#ifdef __cplusplus
}
#endif
#endif
测试
使用上位机发送数据,板子原样返回,测试收发完全正确。
uint8_t buffer[64];
uint32_t len;
while((len = uart_api_read(buffer, sizeof(buffer))) > 0)
{
uart_api_read(buffer,len);
}
- Shell命令行交互
前面我们已经实现了任意红外信号的发送,这样就可以发送任意的红外遥控心好累。
这时已经具备了一个万用红外遥控的雏形,现在开始我们来增加交互功能,实现简单的shell命令行操作。这样我们就可以通过PC或者手机蓝牙串口透传和板子交互,控制板子发送红外遥控信号。
7.1 shell命令行实现
参考一个超级精简高可移植的shell命令行C实现 《https://mp.weixin.qq.com/s/XLmbJn0SKoDT1aLdxHDrbg》,这里直接贴出代码
Shell.c
#include <stdint.h>
#include "shell.h"
shell_read_pf s_input_pf = 0; /* 输入接口指针 */
shell_write_pf s_output_pf = 0; /* 输出接口指针 */
shell_cmd_cfg* s_cmd_cfg_pst = 0; /* 命令列表指针 */
uint8_t s_enableecho_u8 = 0; /* 是否使能echo标志 */
static uint8_t s_cmd_buf_au8[SHELL_CMD_LEN]="\r"; /* 命令缓冲区 */
static uint32_t s_cmd_buf_index_u32 = 0; /* 当前命令缓冲区中字符数 */
/**
* 输出字符接口
*/
static void shell_putchar(uint8_t val)
{
uint8_t tmp;
if(s_output_pf != 0)
{
tmp = val;
s_output_pf(&tmp, 1);
}
}
/**
* 输出字符串接口
*/
static void shell_putstring(char* str)
{
uint32_t len = 0;
uint8_t*p = (uint8_t*)str;
while(*str++)
{
len++;
}
s_output_pf(p, len);
}
/**
* 读字符接口
*/
static int shell_getchar(uint8_t *data)
{
if(s_input_pf == 0)
{
return -1;
}
if(0 == s_input_pf(data, 1))
{
return -1;
}
else
{
return 0;
}
}
/**
* 判断命令字符串的长度
* 命令字符串不能有空格
*/
static uint32_t shell_cmd_len(uint8_t *cmd)
{
uint8_t *p = cmd;
uint32_t len = 0;
while((*p != ' ') && (*p != 0))
{
p++;
len++;
}
return len;
}
/**
* 判断两个字符串是否相等,相等返回0
*/
static int shell_cmd_check(uint8_t *cmd, uint8_t *str)
{
uint32_t len1 = shell_cmd_len(cmd);
uint32_t len2 = shell_cmd_len(str);
if(len1 != len2)
{
return -1;
}
for(uint32_t i=0; i<len1; i++)
{
if(*cmd++ != *str++)
{
return -1;
}
}
return 0;
}
/**
* 读取一行命令
*/
static uint32_t shell_read_line(void)
{
uint8_t ch;
uint32_t count;
/* 初始打印sh> */
if(s_cmd_buf_au8[0]=='\r')
{
shell_putstring("sh>\r\n");
s_cmd_buf_au8[0] = 0;
}
/* 非阻塞读取一个字符 */
if(shell_getchar(&ch) !=0 )
{
return 0;
}
/* 遇到除了退格之外的不可打印字符,则认为收到一行命令
* 退格需要单独处理,需要删除一个字符
*/
if((ch == '\r' || ch == '\n' || ch < ' ' || ch > '~') && (ch != '\b'))
{
if(s_cmd_buf_index_u32==0)
{
/* 缓冲区没有数据就收到了非打印字符串,则打印提示sh> */
shell_putstring("sh>\r\n");
}
else
{
/* 收到了非打印字符,且缓冲区有数据则认为收到了一行
* 返回缓冲区数据长度,并清零计数,打印回车换行
* 并且添加结束符0
*/
count = s_cmd_buf_index_u32;
s_cmd_buf_au8[s_cmd_buf_index_u32]=0;
s_cmd_buf_index_u32 =0;
shell_putstring("\r\n");
return count;
}
}
else
{
if(ch == '\b')
{
/* 退格处理,注意只有有数据才会删除一个字符,添加结束符 */
if(s_cmd_buf_index_u32 != 0)
{
s_cmd_buf_index_u32--;
shell_putchar('\b');
shell_putchar(' ');
shell_putchar('\b');
s_cmd_buf_au8[s_cmd_buf_index_u32]= '\0';
}
}
else
{
/* 可打印字符,添加到缓冲区
* 如果数据量已经到了缓冲区大小-1,则也认为是一行命令
* -1是保证最后有结束符0空间
*/
if(s_enableecho_u8 != 0)
{
shell_putchar(ch);
}
s_cmd_buf_au8[s_cmd_buf_index_u32++] = ch;
if(s_cmd_buf_index_u32>=(sizeof(s_cmd_buf_au8)-1))
{
count = s_cmd_buf_index_u32;
s_cmd_buf_au8[s_cmd_buf_index_u32]=0;
s_cmd_buf_index_u32 =0;
shell_putstring("\r\n");
return count;
}
}
}
return 0;
}
/**
* 搜寻命令列表处理命令
*/
static int shell_exec_cmdlist(uint8_t* cmd)
{
int i;
if(s_cmd_cfg_pst == 0)
{
return -1;
}
for (i=0; s_cmd_cfg_pst.name != 0; i++)
{
if (shell_cmd_check(cmd, s_cmd_cfg_pst.name) == 0)
{
s_cmd_cfg_pst.func(cmd);
return 0;
}
}
if(s_cmd_cfg_pst.name == 0)
{
shell_putstring("unkown command\r\n");
return -1;
}
return 0;
}
/**
* 对外接口,周期执行
*/
void shell_exec(void)
{
if(shell_read_line() > 0)
{
shell_exec_cmdlist(s_cmd_buf_au8);
}
}
/**
* 对外接口,初始化配置接口
*/
void shell_set_itf(shell_read_pf input, shell_write_pf output, shell_cmd_cfg* cmd_list, uint8_t enableecho)
{
s_input_pf = input;
s_output_pf = output;
s_cmd_cfg_pst = cmd_list;
s_enableecho_u8 = enableecho;
}
Shell.h
#ifndef SHELL_H
#define SHELL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#define SHELL_CMD_LEN 64 /**< 命令缓冲区大小 */
typedef void (*shell_command_pf)(uint8_t *); /**< 命令回调函数 */
typedef uint32_t (*shell_read_pf)(uint8_t *buff, uint32_t len); /**< 底层收接口 */
typedef void (*shell_write_pf)(uint8_t *buff, uint32_t len); /**< 底层发接口 */
/**
* \struct shell_cmd_cfg
* 命令信息
*/
typedef struct
{
uint8_t * name; /**< 命令字符串 */
shell_command_pf func; /**< 命令回调函数 */
uint8_t * helpstr; /**< 命令帮助信息 */
}shell_cmd_cfg;
/**
* \fn shell_exec
* 周期调用该函数,读取底层输入,并判断是否有命令进行处理
* 非阻塞
*/
void shell_exec(void);
/**
* \fn shell_set_itf
* 设置底层输入输出接口,以及命令列表
* 调用shell_exec_shellcmd之前,需要先调用该接口初始化
* \param[in] input \ref shell_read_pf 输入接口
* \param[in] output \ref shell_write_pf 输出接口
* \param[in] cmd_list \ref shell_cmd_cfg 命令列表
* \param[in] enableecho 0:不使能回显, 其他值:使能回显
*/
void shell_set_itf(shell_read_pf input, shell_write_pf output, shell_cmd_cfg* cmd_list, uint8_t enableecho);
#ifdef __cplusplus
}
#endif
Shell_func.h
#ifndef SHELL_FUNC_H
#define SHELL_FUNC_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern const shell_cmd_cfg g_shell_cmd_list_ast[ ];
#ifdef __cplusplus
}
#endif
#endif
Shell_func.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "shell.h"
#include "shell_func.h"
#include "uart_api.h"
static void helpfunc(uint8_t* param);
/**
* 最后一行必须为0,用于结束判断
*/
const shell_cmd_cfg g_shell_cmd_list_ast[ ] =
{
{ (uint8_t*)"help", helpfunc, (uint8_t*)"help"},
{ (uint8_t*)0, 0 , 0},
};
void helpfunc(uint8_t* param)
{
(void)param;
unsigned int i;
printf("\r\n");
printf("**************\r\n");
printf("* SHELL *\r\n");
printf("* V1.0 *\r\n");
printf("**************\r\n");
printf("\r\n");
for (i=0; g_shell_cmd_list_ast.name != 0; i++)
{
printf("%02d.",i);
printf("%-16s",g_shell_cmd_list_ast.name);
printf("%s\r\n",g_shell_cmd_list_ast.helpstr);
}
}
7.2 万用遥控-遥控电视开关测试
我们使用前面设计的红外模块接收电视遥控的开关机信号,采集到信号如下
对应如下数据
iostate_t s_iostate[2][40]=
{
{
{0,3000}, /* 0 - 3mS 1000 25uS*/
{1,3000}, /* 1 - 3ms */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2,5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,4000}, /* 1 -4mS */
{0,500}, /* 0- 500uS */
{1,60000}, /* 1 -1000mS */
{0Xff,0}
},
{
{0,3000}, /* 0 - 3mS 1000 25uS*/
{1,3000}, /* 1 - 3ms */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2,5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,1500}, /* 1 -1.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,2500}, /* 1 -2.5mS */
{0,500}, /* 0- 500uS */
{1,4000}, /* 1 -4mS */
{0,500}, /* 0- 500uS */
{1,22500}, /* 1 -22.5mS */
{0Xff,0}
}
};
添加命令行
shell_func.c中
#include "ir.h"
g_shell_cmd_list_ast中添加两行
{ (uint8_t*)"pwron", pwronfunc, (uint8_t*)"pwron"},
{ (uint8_t*)"pwroff", pwrofffunc, (uint8_t*)"pwroff"},
开始申明函数
static void pwronfunc(uint8_t* param);
static void pwrofffunc(uint8_t* param);
实现
void pwronfunc(uint8_t* param)
{
for(int i=0;i<3;i++)
{
ir_send(0);
}
}
void pwrofffunc(uint8_t* param)
{
for(int i=0;i<3;i++)
{
ir_send(1);
}
}
可以看到添加了如下命令
输入pwron on和pwr off就可以发送对应的信号,遥控电视开关机。
详见视频
- @todo
以上实现了万能遥控的基础,后面就是扩展不同的应用方式,比如手机通过蓝牙串口透传发送上述的pwr on和off的命令,或者pc端串口发送等等。
在PC或者手机端可以调用本地语音识别API来实现语音识别,控制板子遥控家电。
后面也可以利用本板优秀的性能本地进行音频采集与语音识别发送对应红外遥控信号,控制各类家电。同时遥控编码可以通过本地红外接收学习或者手机/PC端直接发送。通过以上实现智能家居助手。
- 总结
以上基于STM32H7B3I-DK进行了智能家居助手的应用设计,实现了万能红外遥控的功能,并进行测试演示。分享整个开发过程,包括红外模块软硬件设计,串口通讯,shell交互等等,初步具备应用价值,后面可以继续完善。
红外模块生产资料件附件,可以直接用于打样。《Gerber_PCB3_2023-12-16.zip》
代码见《FreeRTOS_ThreadCreation.zip》,替换en.stm32cubeh7_v1-11-0下的对应目录即可。
视频见
|