关于遥控器程序其实与小四轴的主板程序的主要配置都很类似,而区别只有两处:一个是通信模块NRF24L01中小四轴是信号接收端,而遥控器是发送端;另一个是遥控器只需要采集各个控制杆的位置和状态并转为数字信号发送即可。我们还是先来看看遥控器电路的原理图:
通过PA0~PA3采集4路控制杆位置
接入按钮
实际上,我们使用STM32的AD采集功能将控制杆的位置采集出来。其原理也很简单:控制杆实际上就是一个变阻器,当控制杆在不同位置上时其阻值不同,我们使用STM32的PA0~PA3脚的AD采集其端点的电压即可得到其具体的位置。AD采集的配置项如下:
同样的,我们使用CubeIDE工具生成代码如下:
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = ENABLE;
hadc1.Init.NbrOfDiscConversion = 1;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 4;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_4;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
之后,我们还需要创建一个线程来完成AD采集功能,如下:
#define ADC_TIMEOUT (1000)
#define ADC_FILTER (0.3f)
uint16_t ctl_pwm[ADC_CHS] = { 0 };
uint8_t ctl_pwm_reverse[ADC_CHS] = { 0, 0, 0, 1 };
float adc_value[ADC_CHS] = { 0 };
static float adc_value_pre[ADC_CHS] = { 0 };
extern ADC_HandleTypeDef hadc1;
void* adc_pthread(void* arg)
{
uint16_t value[ADC_CHS] = { 0 };
HAL_ADCEx_Calibration_Start(&hadc1);
uint32_t tk = 0;
char buff[100] = { 0 };
while (1)
{
for (int i = 0; i < ADC_CHS; i++)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, ADC_TIMEOUT);
value[i] = HAL_ADC_GetValue(&hadc1);
float v = (float)value[i] / 4096.0f;
if (ctl_pwm_reverse[i])
{
v = 1.0f - v;
}
adc_value[i] = v * ADC_FILTER + adc_value_pre[i] * (1.0f - ADC_FILTER);
adc_value_pre[i] = adc_value[i];
ctl_pwm[i] = adc_value[i] * (CTL_PWM_MAX - CTL_PWM_MIN) + CTL_PWM_MIN;
}
HAL_ADC_Stop(&hadc1);
tk++;
sleep_ticks(2);
}
return NULL;
}
void adc_task(void)
{
pcb_create(PCB_ADC_PRIO, &adc_pthread, NULL, PCB_ADC_SIZE);
}
之后再将这些采集到的数字信号通过NRF24L01发送出去即可,我们来看一下其发送代码:
void* nrf2401_pthread(void* arg)
{
uint8_t buff_ctl[FIXED_PACKET_LEN] = { 0 };
buff_ctl[0] = 0xA5;
buff_ctl[1] = 0x5A;
NRF24L01_check();
RF24L01_Init();
RF24LL01_Write_Hopping_Point(64);
NRF24L01_Set_Power(POWER_0DBM);
NRF24L01_Set_Speed(SPEED_250K);
uint32_t tk = 0;
while (1)
{
ctl_set_offset();
uint16_t btn = 0x0;
if (ctl_btn[8] == 1)
{
btn |= 1;
}
if (ctl_btn[9] == 1)
{
btn |= (1 << 1);
}
if (ctl_btn[10] == 0)
{
btn |= (1 << 2);
}
if (ctl_btn[11] == 0)
{
btn |= (1 << 3);
}
int ind = 2; // HEAD0 HEAD2
buff_ctl[ind++] = pwm[0] & 0xff;
buff_ctl[ind++] = (pwm[0] >> 8) & 0xff;
buff_ctl[ind++] = pwm[1] & 0xff;
buff_ctl[ind++] = (pwm[1] >> 8) & 0xff;
buff_ctl[ind++] = pwm[2] & 0xff;
buff_ctl[ind++] = (pwm[2] >> 8) & 0xff;
buff_ctl[ind++] = pwm[3] & 0xff;
buff_ctl[ind++] = (pwm[3] >> 8) & 0xff;
buff_ctl[ind++] = btn & 0xff;
buff_ctl[ind++] = (btn >> 8) & 0xff;
uint16_t crc = calc16crc(buff_ctl, ind); //除CRC之外
buff_ctl[ind++] = crc & 0xff;
buff_ctl[ind++] = (crc >> 8) & 0xff;
if (tx_send == 0)
{
RF24L01_Set_Mode(MODE_TX);
NRF24L01_TxPacket(buff_ctl, FIXED_PACKET_LEN);
tx_send = 1;
}
sleep_ticks(20);
}
return NULL;
}
void nrf2401_task(void)
{
pcb_create(PCB_NRF_PRIO, &nrf2401_pthread, NULL, PCB_NRF_SIZE);
}
需要说明的,我们采用了一个自定义协议将其打包成一个具有14个字节的数据包并通过NRF24L01发送给接收端,其中包含了4路控制杆的位置信号(油门、滚转、俯仰、偏航)以及2路开关信号。此外,为了让控制在居中时给小四轴发送的信号也处在中位,因此我们使用了8个微调按钮分别用来对油门、滚转、俯仰、偏航这4路信号做微调,并保存到STM32的FLASH当中每次上电再将这些内容读取出来,这部分代码我们在文章中省略,读者可以在我们的文章最后找到完成的代码下载。
最后我们给出完整的程序代码:下载地址
Copyright © 2015-2023 问渠网 辽ICP备15013245号