最新心血来潮,看着手里的一块STM32开发板和几个AT89C4051和nRF24L01无线模块,准备利用它们做成一套无线控制系统。由于nRF24L01无线模块的特点,打算利用不同的物理地址,通过程序控制,实现组网控制。有了想法,开始着手设计。
首先是主控制系统,使用早期入手的智嵌STM32开发板。为啥要用这块板子呢?因为这个开发板上已经设置nRF24L01接口,不用单独设计了,拿来直接使用就行,很方便。
各个被控制的分机,使用AT89C4051。其实更想用STC家的产品,因为有看门狗,在无人值守的时候,更适合对应程序跑飞这种情况。奈何手里的这几片AT89C4051实在是闲置太久,本着不想浪费的原则,就只好将就一下了。为了防止程序跑飞,只能再考虑在各个分机的公用电源控制上,加一个单独的有看门狗的电源控制模块。
主控制板的图片如下:
网口下面的那个8口双排插座的位置就是给nRF24L01用的。可以直接插上nRF24L01使用。而且卖家还提供了例程,例程里就有nRF24L01的,拿来改改就能用。话说“天下**一大抄,抄来抄去有提高”,其实程序也是这样,哈哈哈,题外话了。作为测试,修改目标是,按下开发板上四个按键中的某一个时,向地址匹配的分机发送信号。主机加载nRF24L01的样子如下:
分机的样子如下:
1对应的是接收信号用的nRF24L01模块。
2对应的外部测量信号输入端,配合AT89C4051的比较器使用。如果使用可以互换的STC芯片,这个地方可以用AD输入作为动作条件。
3对应的驱动外部继电器模块。提供5V、GND、驱动信号。驱动信号低电平有效。
4对应的是串口输入输出。主要是开发阶段测试用。实际应用时,可以不用串口。空出P3.0和P3,1给U3(24C64)作为控制信号用。
5对应的是拨码开关。目的是设置分机的地址。这个地址是和主控板的K1-K4一一对应的。
挂载了nRF24L01模块、继电器模块、连接串口的分机,如下图所示:
主控板主处理代码如下:
int main(void) {
unsignedchar i=0, kv=0, tmp_buf[TX_PLOAD_WIDTH], addr1=0, addr2=0, errorFlag=0;
/* 串口初始化 */
Printf_Init();
SysTick_Configuration(); //系统初始化
Key_Configuration(); // 初始化按键用到的GPIO口
LED_Configuration(); // 初始化LED用到的GPIO口
NVIC_Configuration();
NRF24L01_Init(); // 初始化处理器与NRF24L01连接的GPIO口
while(NRF24L01_Check()) { //24L01在线检测
printf("Didn't find NRF24L01,Please check whether NRF24L01 is online!\n\r");
delay_ms(2000);
}
printf("NRF24L01 OK\n\r");
/*****熄灭四个led灯******/
LED1_OFF;
LED2_OFF;
LED3_OFF;
LED4_OFF;
RX_Mode(kv); //默认接收模式
// 打印本机地址
NRF24L01_Read_Buf(RX_ADDR_P0, tmp_buf, RX_ADR_WIDTH);
// 输出到串口
printf("Receive address of channel 0:");
for (i=0;i<5;i++) {
printf("%c", tmp_buf);
}
printf("\n\r");
while(1){
kv= Key_Value(); //读取按键值
if(kv) { //如果有键按下,则进入到发送模式,并将键值发送出去
//设置发送数据
tmp_buf[0]=0xC0; // 指令码
tmp_buf[1]=0x00; // 通讯数据1
tmp_buf[2]=0x14; // 通讯数据2
tmp_buf[3]=0x05; // 通讯数据3
tmp_buf[4]=getVerCode(tmp_buf, 0, 3);
// 根据按键,设置目标分机地址
addr1=TX_ADDRESS[3] + kv/10;
addr2=TX_ADDRESS[4] + kv%10;
// 转为发射模式
TX_Mode(kv);
if(NRF24L01_TxPacket(tmp_buf)== TX_OK) //如果发送成功
{
printf("Sendingdata...\n\r");
}else {
printf("Transfer failed.\n\r");
}
RX_Mode(kv); //发送完成后进入到接收模式
}
//如果接收成功,则点亮相应的LED
if(NRF24L01_RxPacket(tmp_buf) == 0) {
// 对方发射的目标机是本机?指令码是否正确?
if (tmp_buf[0]==addr1 && tmp_buf[1]==addr2 &&tmp_buf[2]==0xC1) {
// 判断校验码
if (tmp_buf[3]!=8) {
printf("Wrong datalength.\n\r");
errorFlag=1;
} else {
if(tmp_buf[8]!=getVerCode(tmp_buf, 0, 7)) {
printf("Errorcheck code.\n\r");
errorFlag=1;
} else {
// 根据对方分机的编号,点亮对应的LED
i=(addr1-0x30)*10 +(addr2-0x30);
if (i>0 && i< 5) {
One_LED_ON(i); //点亮LED
} else {
printf("Wrongaddress of sub-device.\n\r");
errorFlag=1;
}
}
}
} else {
errorFlag=1;
}
if (errorFlag == 1) {
// 全亮,表示出错:可能数据错误,可能分机编号不对
One_LED_ON(1);
One_LED_ON(2);
One_LED_ON(3);
One_LED_ON(4);
// 输出到串口
printf("Receiveddata:\r\n");
for(i=0;i<TX_PLOAD_WIDTH;i++) {
printf("%2x ",tmp_buf);
}
printf("\n\r");
}
}
}
}
主机nRF24L01模块的部分处理代码:
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:发送完成状况
u8 NRF24L01_TxPacket(u8 *txbuf) {
u8state, i;
// 输出到串口
printf("Sent data:");
for (i=0;i<TX_PLOAD_WIDTH;i++) {
printf("%2x ", txbuf);
}
printf("\n\r");
Clr_NRF24L01_CE;
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
Set_NRF24L01_CE; //启动发送
while(READ_NRF24L01_IRQ!=0); //等待发送完成
state=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(SPI_WRITE_REG+STATUS,state); //清除TX_DS或MAX_RT中断标志
if(state&MAX_TX) //达到最大重发次数
{
NRF24L01_Write_Reg(FLUSH_TX,0xff); //清除TX FIFO寄存器
returnMAX_TX;
}
if(state&TX_OK) //发送完成
{
NRF24L01_Write_Reg(FLUSH_TX,0xff); //清除TX FIFO寄存器
returnTX_OK;
}
return0xff; //其他原因发送失败
}
//启动NRF24L01接收一次数据
//rxbuf:待接收数据首地址
//返回值:0,接收完成;其他,错误代码
u8 NRF24L01_RxPacket(u8 *rxbuf) {
u8state, i;
state=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(SPI_WRITE_REG+STATUS,state);//清除TX_DS或MAX_RT中断标志
if(state&RX_OK) //接收到数据
{
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
NRF24L01_Write_Reg(FLUSH_RX,0xff); //清除RX FIFO寄存器
// 输出到串口
printf("Received data::");
for (i=0;i<TX_PLOAD_WIDTH;i++) {
printf("%2x ", rxbuf);
}
printf("\n\r");
return0;
}
return1; //没收到任何数据
}
//该函数初始化NRF24L01到RX模式
//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入RX模式,并可以接收数据了
void RX_Mode(u8 offset) {
u8 addr[RX_ADR_WIDTH],i;
for (i=0;i<RX_ADR_WIDTH;i++) {
addr=RX_ADDRESS;
}
// 根据预设子机地址,重新设定主机无线模块地址
addr[RX_ADR_WIDTH-2]=offset/10 + addr[RX_ADR_WIDTH-2];
addr[RX_ADR_WIDTH-1]=offset%10 + addr[RX_ADR_WIDTH-1];
Clr_NRF24L01_CE;
//写RX节点地址
NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P0,(u8*)addr,RX_ADR_WIDTH);
// 自动应答
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA,0x01);
//使能通道0的接收地址
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_RXADDR,0x01);
//设置RF通信频率
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_CH,40);
//选择通道0的有效数据宽度
NRF24L01_Write_Reg(SPI_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);
//设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_SETUP,0x0f);
//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,PRIM_RX接收模式
NRF24L01_Write_Reg(SPI_WRITE_REG+CONFIG,0x0f);
//CE为高,进入接收模式
Set_NRF24L01_CE;
}
//该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,
//选择RF频道,波特率和LNA HCURR PWR_UP,CRC使能
//当CE变高后,即进入RX模式,并可以接收数据了
//CE为高大于10us,则启动发送.
void TX_Mode(u8 offset) {
u8 addr[TX_ADR_WIDTH],i;
// 根据预设子机地址,重新设定主机无线模块地址
for (i=0;i<TX_ADR_WIDTH;i++) {
addr=TX_ADDRESS;
}
addr[TX_ADR_WIDTH-2]=offset/10 + addr[TX_ADR_WIDTH-2];
addr[TX_ADR_WIDTH-1]=offset%10 + addr[TX_ADR_WIDTH-1];
// 输出到串口
printf("Target sub-device address:");
for (i=0;i<TX_ADR_WIDTH;i++) {
printf("%2x ", addr);
}
printf("\n\r");
Clr_NRF24L01_CE;
//写TX节点地址
NRF24L01_Write_Buf(SPI_WRITE_REG+TX_ADDR,(u8*)addr,TX_ADR_WIDTH);
//设置TX节点地址,主要为了使能ACK
NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P0,(u8*)addr,RX_ADR_WIDTH);
//使能通道0的自动应答
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA,0x01);
//NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA,0x00);
//使能通道0的接收地址
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_RXADDR,0x01);
//设置自动重发间隔时间:‘1111’-等待 4000+86us;最大自动重发次数:3次
NRF24L01_Write_Reg(SPI_WRITE_REG+SETUP_RETR,0xf3);
//设置RF通道为40
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_CH,40);
//设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_SETUP,0x0f);
//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,PRIM_RX发送模式,开启所有中断
NRF24L01_Write_Reg(SPI_WRITE_REG+CONFIG,0x0e);
// CE为高,10us后启动发送
Set_NRF24L01_CE;
}
分机主处理代码:
void main (void) {
bit et0Status=ET0;
u8 i=0, state=0;
// 初始化
system_init();
transstr("main start...\r\n");
// 初始化nRF24L01关联口线
nRF24L01P_Init();
if(NRF24L01_Check()==1) { //24L01在线检测
transstr("Didn'tfind NRF24L01,Please check whether NRF24L01 is online!\r\n");
delay_ms(300);
}
transstr("slaver-NRF24L01 OK\r\n");
// 接收模式
nRF24L01P_RX_Mode();
// 主循环
while (1) {
// 等待nRF2*4L01的中断信号,表明收到了来自主机的指令
if (NRF24L01_IRQ==0) {
transstr("main_NRF24L01_interrupt:\r\n");
// 保存当前定时器0中断状态,
et0Status=ET0;
// 禁止Timer0的中断
ET0=0;
TR0=0;
// 检查在中断类型
state=nRF24L01P_Read_Reg(REG_STATUS);
if(state & RX_DR) {
// 收到来自nRF24L01的中断请求,开始接收nRF24L01的数据
if(!nRF24L01P_RxPacket(rx_buf)){
// 检查数据是什么指令,并执行对应的动作
AnalysisCommand();
} else {
// 没有收到数据?
}
}
// 达到最大重发次数
if(state&MAX_RT) {
// 清除TX_DS或MAX_RT中断标志
nRF24L01P_Write_Reg(WRITE_REG +REG_STATUS, state);
// 清除nRF24L01的发送缓冲区
nRF24L01P_Write_Reg(FLUSH_TX,0xff);
}
// 发送完成
if(state&TX_DS) {
// 清除TX_DS或MAX_RT中断标志
nRF24L01P_Write_Reg(WRITE_REG +REG_STATUS, state);
// 清除nRF24L01的发送缓冲区
nRF24L01P_Write_Reg(FLUSH_TX,0xff);
}
if (et0Status) {
ET0=1;
TR0=1;
}
}
}
}
到这里,简单地使用STM32和51单片机,以nRF24L01实现组网通讯的实验,就完成了。后续准备在STM32开发板上增加系统设置、液晶显示等功能,实现动态地控制子设备的控制。比如用子设备控制什么时候开关灯、开关多久灯。本套系统中,因为使用了4位拨码开关来控制分机地址,所以是可以组建1个主机、16个子机的小型无线网络,当然需要修改下主机程序,以适应16个子机的选址。
实际上,以nRF24L01组网,除了改变物理地址方式实现,还可以使用其它方式,比如开放其它通道;比如使用相同的物理地址,采用禁止应答,在通讯数据中增设逻辑地址检查。逻辑地址一致的才应答。灵活使用nRF24L01,可以很方便的制作小型家用电子网络应用。
---------------------
作者:suncat0504
链接:https://bbs.21ic.com/icview-3191322-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。