引入
本人前段时间在做智能车竞赛时碰到一个串口通信的任务,具体是要实现OpenMV端与MCU端的通信。现在看来这是一个比较简单且比较基础的任务,但当时我作为一个从来没有写过通信任务的小白,在完成这个任务的过程中还是遇到了很多问题的,现在就来跟大家分享一下我写UART通信的过程中遇到的问题以及我是怎么解决的。
代码实现
开始分享前先分别放上MCU端与OpenMV端的代码。
MCU端(收):
void uart_rx_interrupt_handler(void)//中断函数
{
uart_query_byte(UART_INDEX, &get_data); // 查询式接收数据
fifo_write_buffer(&uart_data_fifo, &get_data, 1); // 将数据写入 fifo 中
}
void get_uartdata(void)
{
fifo_data_count = fifo_used(&uart_data_fifo); // 查看 fifo 是否有数据
if(fifo_data_count != 0)
{
if(get_states==0)
{
fifo_read_buffer(&uart_data_fifo, fifo_get_data, &fifo_data_count, FIFO_READ_AND_CLEAN);
if(fifo_get_data[0]==0xB7) //包头0xB7
{
get_states=1;
}
else get_states=0;
fifo_get_data[0]=0;
}
else if(get_states==1)
{
fifo_read_buffer(&uart_data_fifo, fifo_get_data, &fifo_data_count, FIFO_READ_AND_CLEAN);
memcpy(my_data, fifo_get_data, sizeof(my_data));
get_states=2;
}
else if(get_states==2)
{
fifo_read_buffer(&uart_data_fifo, fifo_get_data, &fifo_data_count, FIFO_READ_AND_CLEAN);
if(fifo_get_data[0]==0x99) 包尾0x99
{
get_states=0;
uart_write_string(UART_INDEX, "get");
fifo_get_data[0]=0;
}
else
{
get_states=0;
fifo_get_data[0]=0;
}
}
}
}
OpenMV端(发):
baotou=[0xB7]#包头
baowei=[0x99]#包尾,都是用于验证数据集的准确性
def send_data(data):
uart.write(bytearray(baotou))
time.sleep_ms(100)
uart.write(bytearray([data]))
time.sleep_ms(100)
uart.write(bytearray(baowei))
time.sleep_ms(100)
MCU端(发):
/*在这次的任务中MCU端对OpenMV端发送数据的要求不高,
发送数据的目的仅仅是作为接收到OpenMV端数据的一种响应,所以只要调用库里对应的write函数就行*/
uart_write_string(UART_INDEX, "get");
//一定要注意不能连续给同一个串口写数据,最好要有100ms以上的时间间隔,不然数据容易出错
OpenMV端(收):
get_signal = 'get' #MCU正确接收到数据之后返回字符串“get”,get_signal用于验证MCU端是否正确读取数据
uart_num = uart.any() # 获取当前串口数据数量
if(uart_num):
get_data = uart.read(uart_num).strip().decode() # 读取串口数据
time.sleep_ms(100)#给足够的时间来读取uart发送来的数据
if (get_data ==get_signal):
……
代码解析
MCU端(收)
1. 首先要将MCU读取串口数据的函数放在对应的UART中断里,且要将UART中断的优先级设置为最高,这样才能保证单片机能及时读取串口的数据而不造成数据丢失。我这里直接调用了逐飞库里对应的读串口数据函数。大家可以看到我MCU端的UART通信是基于fifo的,大家可以通过下面这篇文章来了解基于fifo的UART通信:UART的FIFO功能
2. 成功读取到数据之后,需要对数据进行解析。解析方法要和你定义的数据包格式相适应。我这里的数据包格式是“包头+所需数据+包尾”。
3. 包头和包尾的内容可以自定义,最好定义为“所需数据”里不可能出现的数。需要特别注意的是,接收端认为的包头和包尾一定要和发送端定义的包头和包尾一致,不一致的话接收端不能正常解析数据。
4. 在get_uartdata函数中,我将数据解析过程分为三种状态。状态1是解析串口数据的包头,如果和自定义数据包的包头不一致,那么这个数据一定不是我需要的,丢弃。如果是我所需要的,则进入“所需数据”读取状态,即状态2。读取完成之后进入状态3解析包尾,如果该数据包的包尾与自定义包尾不一致,则说明UART通信的过程中数据受到了干扰或出现了丢包异常,此时整段数据丢弃,重新进入状态1。
OpenMV端(发):
OpenMV端作为发送端时,只需要定义好包头包尾,然后使用uart.write函数将数据发出即可。
MCU端(发):
因为在我这里MCU端只需要发送一个字符串“get”来作为成功接收到“所需数据”之后的响应,所以就没有对“get”这个数据添加包头包尾,实际测试过程中也没有出现过错误。(可能是因为发送的数据并不多,如果读者发送的数据比较多的话,最好还是加上包头包尾来校验。)
OpenMV端(收):
这里有一点是需要特别注意的,OpenMV端直接接收到的数据是字节串的格式,还不能和我们的字符串进行比较匹配,需要对数据进行解码,不解码就直接进行比较是会报错的。
uart.read(uart_num).strip().decode()
.strip()的意思是先对字符串变量进行去除首尾空白符操作
.decode()的意思是将目标二进制数据bytes转为目标字符串str类型,为解码过程。
优化改进
在写这篇文章的时候,我突然发现OpenMV端发送和MCU端接收的代码和逻辑都可以优化一下。
数据收发的思路还是不变的,数据包的格式也不变。
OpenMV端(发),修改如下:
baotou=[0xB7]#包头
baowei=[0x99]#包尾,都是用于验证数据集的准确性
def send_data(data):
data_packet = []
data_packet.append(baotou)
data_packet.append(data)
data_packet.append(baowei)
uart.write(bytearray(data_packet))
time.sleep_ms(100)
这样才是真正将包头、所需数据、包尾打包成一个数据包发送,OpenMV端发送数据的次数从3次变成了1次,MCU端也只需要进入一次UART中断并解析一次数据包,效率大大提高。
MCU端(收)的修改就是在原来的基础上把数据解析的操作一体化(原来是分三次进行,if 和else if不能同时满足,所以是三次):先匹配包头,接着接收数据,然后在接收到的数据中搜索并匹配包尾(可以使用for循环逐个匹配),找到包尾后发送“get”响应OpenMV。MCU端(收)的优化思路大概就是这样,代码就不演示了,不会太难,读者有需要的话可以在原来MCU接收端代码的基础上修改。
以上就是本人解决MCU端与OpenMV端之间UART通信的思路与策略,读者如有任何疑问都可以在评论区留言。感谢阅读!