这部分主要实现的是网络控制舵机。
前面写了一个网络应用,在网络应用的代码上加以修改增加舵机控制功能。
在写之前先添加一个模块,AT24C256存储模块。
因为无法获取舵机的实时角度,每次启动都要重新找位置,加一个存储模块将舵机的角度保存,需要时调出。
AT24C256分512页,每页64个字节,能存储32,768个字节。用I2C作为通信接口,1个I2C端口可以串联多个设备,用设备地址区分它们,对于端口少的小设备真的是非常友好。
AT24C256的读写非常简单,输入写指令 + 要写入的地址 + 写入数据,就可以写入,读出同理。
AT24C存储容量有很多型号,我就选一个价格差不多,容量最大的型号,AT24C256。
应为容量大一点,地址1个字节长度不够,这个模块的物理地址用2个字节表示。
15bit的地址数据对其寻址,低6bit(D5-D0)为页内字节单元地址,高9bit(D14-D6)为页地址。
| * 0 0 0 0 0 0 0 | 0 0 . 0 0 0 0 0 0 |
hi_void At24c256_I2C_Write_Byte(hi_u16 y, hi_u8 x, hi_u8 b)
{
hi_u32 stat = 0;
hi_u8 addr1 = 0xff;
hi_u8 addr2 = 0xff;
addr1 = addr1 & (y >> 2);
addr2 = addr2 & (y << 6);
addr2 = addr2 + x;
hi_u8 send_buff[3] = {addr1, addr2, b};
hi_i2c_data at24c256_i2c_data = {0};
at24c256_i2c_data.send_buf = send_buff;
at24c256_i2c_data.send_len = 3;
stat = hi_i2c_write(HI_I2C_IDX_0, AT24C256_WRIT, &at24c256_i2c_data);
hi_udelay(10 * 1000);
if(stat != HI_ERR_SUCCESS)
{
printf(" [At24c256_I2C_Write_Byte] hi_i2c_write = Failed \n");
return;
}
}
hi_void At24c256_I2C_Read_Byte(hi_u16 y, hi_u8 x, hi_u8 *b)
{
hi_u32 stat = 0;
hi_u8 addr1 = 0xff;
hi_u8 addr2 = 0xff;
addr1 = addr1 & (y >> 2);
addr2 = addr2 & (y << 6);
addr2 = addr2 + x;
hi_i2c_data at24c256_i2c_data = {0};
hi_u8 send_buff[2] = {addr1, addr2};
at24c256_i2c_data.send_buf = send_buff;
at24c256_i2c_data.send_len = 2;
stat = hi_i2c_write(HI_I2C_IDX_0, AT24C256_WRIT, &at24c256_i2c_data);
hi_udelay(10 * 1000);
if(stat != HI_ERR_SUCCESS)
{
printf(" [At24c256_I2C_Read_Byte] hi_i2c_write = Failed \n");
return;
}
hi_u8 rece_buff[] = {0};
at24c256_i2c_data.receive_buf = rece_buff;
at24c256_i2c_data.receive_len = 1;
stat = hi_i2c_read(HI_I2C_IDX_0, AT24C256_READ, &at24c256_i2c_data);
if(stat != HI_ERR_SUCCESS)
{
printf(" [At24c256_I2C_Read_Byte] hi_i2c_read = Failed \n");
return;
}
*b = rece_buff[0];
}
hi_void At24c256_I2C_Write_Data(hi_u16 y, hi_u8 x, hi_u8 *d, hi_u8 l)
{
hi_u8 addr1 = 0xff;
hi_u8 addr2 = 0xff;
addr1 = addr1 & (y >> 2);
addr2 = addr2 & (y << 6);
addr2 = addr2 + x;
hi_u8 send_data[l + 2];
send_data[0] = addr1;
send_data[1] = addr2;
for(hi_u8 i=0; i<l; i++)
{
send_data[i+2] = d[i];
}
hi_i2c_data at24c256_i2c_data = {0};
at24c256_i2c_data.send_buf = send_data;
at24c256_i2c_data.send_len = l + 2;
hi_u32 ret = hi_i2c_write(AT24C256_I2C_IDX, AT24C256_WRIT, &at24c256_i2c_data);
hi_udelay(10 * 1000);
if(ret != HI_ERR_SUCCESS)
{
printf(" [At24c256_I2C_Write_Data] hi_i2c_write = Failed \n");
return;
}
}
hi_void At24c256_I2C_Read_Data(hi_u16 y, hi_u8 x, hi_u8 *d, hi_u8 l)
{
hi_u8 addr1 = 0xff;
hi_u8 addr2 = 0xff;
addr1 = addr1 & (y >> 2);
addr2 = addr2 & (y << 6);
addr2 = addr2 + x;
hi_u8 send_data[2] = {0};
send_data[0] = addr1;
send_data[1] = addr2;
hi_i2c_data at24c256_i2c_data = {0};
at24c256_i2c_data.send_buf = send_data;
at24c256_i2c_data.send_len = 2;
hi_u32 ret = hi_i2c_write(AT24C256_I2C_IDX, AT24C256_WRIT, &at24c256_i2c_data);
if(ret != HI_ERR_SUCCESS)
{
printf(" [At24c256_I2C_Read_Data] hi_i2c_write = Failed \n");
return;
}
hi_u8 rece_data[l];
at24c256_i2c_data.receive_buf = rece_data;
at24c256_i2c_data.receive_len = l;
ret = hi_i2c_read(AT24C256_I2C_IDX, AT24C256_READ, &at24c256_i2c_data);
if(ret != HI_ERR_SUCCESS)
{
printf(" [At24c256_I2C_Read_Data] hi_i2c_read = Failed \n");
return;
}
memcpy(d, rece_data, l);
}
比如向第10页第5位写入10个字节的数据,就是At24c256_I2C_Write_Data(10, 5, datas, 10);
网络控制跟遥控器控制是差不多的,在主循环里不断接收网络信息,有运行状态就运行舵机。
if(net_stat == 1)
{
// 接收数据
SE_Recv_Data();
}
if(Get_SE_State() == 1)
{
// 舵机 运行
Engine_Run(2);
Run_Wait(500 * 100);
}
在舵机运行里加个参数,一次跨1度慢,跨多少根据需要设,2就是一次跨2度。
网络控制这个部分根据需要灵活控制。
// 发送数据
hi_u8 SE_Send_Data(hi_u8 i)
{
memset(send_buff, 0, 20);
send_buff[19] = i;
SE_To_Bytes(&send_buff, se_ps[se_pi]);
int ret = TCP_Send(client_sock, send_buff, 20);
if(ret > 0)
{
return (hi_u8)ret;
}
if(ret == -1)
{
printf(" Send ERROR \n");
return 0;
}
}
// 接收数据
hi_u8 SE_Recv_Data(hi_void)
{
int ret = TCP_Recv_Nonblocking(client_sock, recv_buff, 20);
if(ret > 0)
{
Net_Control(recv_buff[17], recv_buff[18]);
return (hi_u8)ret;
}
return 0;
}
// 网络控制
hi_u8 Net_Control(hi_u8 n, hi_u8 c)
{
// 舵机号
se_pi = n;
// 接收数据
if(c == 1)
{
printf("\n [1] Receive Data : \n");
// 接收数组 转 舵机数据
Bytes_To_SE(recv_buff, &se_ps[se_pi]);
// 回传
SE_Send_Data(1);
}
// 发送数据
if(c == 2)
{
printf("\n [2] Send Data : \n");
// 回传
SE_Send_Data(2);
}
//
if(c == 3)
{
printf("\n [3] Read Data : \n");
// 读取存储卡数据 转到 舵机数据
memset(se_buff, 0, 20);
At24c256_I2C_Read_Data(se_pi, 0, se_buff, 20);
Bytes_To_SE(se_buff, &se_ps[se_pi]);
// 回传
SE_Send_Data(3);
}
if(c == 4)
{
printf("\n [4] Save Data : \n");
// 将舵机数据 储存到存储卡
memset(se_buff, 0, 20);
SE_To_Bytes(&se_buff, se_ps[se_pi]);
At24c256_I2C_Write_Data(se_pi, 0, se_buff, 20);
// 回传
SE_Send_Data(4);
}
if(c == 5)
{
printf("\n [5] Stop : \n");
se_ps[se_pi].run = 0;
SE_Send_Data(5);
}
if(c == 6)
{
printf("\n [6] Angel Init : \n");
// 接收数组 转 舵机数据
Bytes_To_SE(recv_buff, &se_ps[se_pi]);
// 旋转舵机
PCA9685_Angle(se_ps[se_pi].pin, se_ps[se_pi].ang, se_ps[se_pi].ran);
PCA9685_Set_PWM(se_ps[se_pi].pin, 0, 0);
SE_Send_Data(6);
}
if(c == 7)
{
printf("\n [7] Turn : \n");
// 将 舵机数据 回传
SE_Send_Data(7);
}
Display_SE_Data();
}
现在的控制内容主要还是以测试为主,控制功能监测、试舵机连接运行等等。写到这里也发现个问题,前面写的时候考虑后面的修改,代码环节太多,代码冗余太高,堆到一定程度,能感觉运行变得不可靠,所以后面定型重新整理一下。
写到这里想说一个我写代码的原则,第一遍代码是给自己看的,用人的思维方式写,方便自己看,面向过程。第二遍是给别人看的,因为过一段时间自己再修改,已经看不懂自己写的繁琐过程了,还要重新理顺一遍,这个时候代码考虑结构性,方便可读,能重用,容易修改,面向结构,最后一遍是给机器运行用的,删除冗余代码过程化,提高运行效率,面向设备。
网络控制写一个Window下的上位机实现,上位机用C#写,C#我是一点都没学过,以前写过Java,用C#感觉非常容易,当然没系统学过就是看工具提示写,代码肯定有很多不太合理。出于方便阅读用中文命名函数名,这个真的很好,用起来舒服多了。
运行效果
具体功能看代码吧,看不懂的可以发信息。
有很多朋友没有积分,我用百度网盘发一份。
链接: https://pan.baidu.com/s/17ApzDqpXmx5dMnvi388dQQ?pwd=xxni 提取码: xxni