ESP-IDF下的软件串口softserial库

ESP-IDF下的软件串口softserial库

前言

​ 最近在鼓捣esp-idf,想把原本arduino的项目重构到idf框架下,但是在第一步我就犯了难,我发现没有一个idf写的软件串口库可以用,我就寻思着自己写了一个。现在已经完成最简单版本的搭建,实现了基础功能(现只验证了esp-idf v5.21 + esp32s3,高波特率下并不稳定,现只支持8N1)。由于本身实力不足,并且是第一次写开源库,存在许多错误,还请大家批评指正。

项目网址 : https://github.com/1StarStarFire1/ESPIDF-Softserial-UART

回顾串口时序

常用通信时序之UART、IIC、SPI(基于STM32)_串口时序图-优快云博客

​ 以最常用的8-N-1为例子(八数据位,一位停止位,零位校验位,正常逻辑电平),其中各个位的持续时间为:1/波特率。 空闲状态为高电平,起始位为低电平,停止位为高电平,D0-D7低位先行,高位在后。

时间处理

为了实现精确的控制,我们通过波特率计算每一个数据位的CPU周期

/* 
 * 计算每位时间(CPU周期)
 * @param baud_rate 波特率
 * @return 每位对应CPU周期数
 * @note 乘以2是分频机制
 */
uint64_t calculate_bit_time(uint32_t baud_rate) 
{
    //uint64_t bit_time_us = (1000000UL / baud_rate); //原本计算会有四舍五入积累误差,故舍去

    return 2 *  (CPU_CLK_FREQ / baud_rate);  // 转换为CPU周期数
    //CPU_CLK_FREQ = 80Mhz 但是 cpu经过倍频 其真正频率为 = 160Mhz 通过使用	   esp_cpu_get_cycle_count()可以验证其频率
}

通过获取当前CPU时序,进行时间阻塞式延迟(高精度要求,不适合使用实时操作系统),其中bit_cycles为calculate_bit_time()的计算结果

/* 
 * 精确等待指定CPU周期
 * @param star 指向起始时间的指针(自动更新)
 * @param bit_cycles 需要等待的周期数
 * @notestar 变为+=bit_cycles
 */
void wait_time(uint64_t* star , uint64_t bit_cycles)
{
    uint64_t end = *star + bit_cycles;
    while(esp_cpu_get_cycle_count() < end);
    *star = end;
}

接收处理

为了实现数据接收,我们根据时序图进行如下布置
数据接收
部分代码如下

/* 
 * 接收中断服务例程(ISR)
 * @param arg 软件UART结构体指针
 * @note 必须在临界区中处理中断
 */
void IRAM_ATTR software_uart_rx_isr(void* arg) 
{
    SoftwareUART* uart = (SoftwareUART*)arg;
    portBASE_TYPE higher_priority_task_woken = pdFALSE;

    // 进入临界区
    portENTER_CRITICAL_ISR(&uart->lock);
    // 检测起始位(下降沿触发)
    if (gpio_get_level(uart->rx_pin) == 0) 
    {
        uint8_t data = 0;
        uint64_t star = esp_cpu_get_cycle_count();
        wait_time(&star , (uart->bit_cycles)/3);//除以二保证在bit中位读取数据
        if(gpio_get_level(uart->rx_pin) == 0)//起始位
        {
            // 读取8位数据(LSB到 MSB)
            for (int i = 0; i < 8; i++) 
            {
                wait_time(&star , uart->bit_cycles);  // 等待至采样点
                data |= (gpio_get_level(uart->rx_pin) << i);
                // 等待下一周期
            }
            // 检查停止位(应为高电平)
            wait_time(&star , uart->bit_cycles);
            if (gpio_get_level(uart->rx_pin) == 1) //停止位
            {
                // 存入缓冲区
                uint16_t next_head = (uart->rx_buffer.head + 1) % uart->rx_buff_size;
                if (next_head != uart->rx_buffer.tail) 
                {
                    uart->rx_buffer.buffer[uart->rx_buffer.head] = data;
                    uart->rx_buffer.head = next_head;
                } 
            }
        }
        
    }
    portEXIT_CRITICAL_ISR(&uart->lock);
}

发送处理

数据发送的思想与发送几乎一致,如下图

数据发送

部分代码如下

/* 
 * 发送一个字节(8位数据 + 起始位 + 停止位)
 * @param uart 软件UART结构体指针
 * @param data 需要发送的8位数据
 */
void software_uart_tx_byte(SoftwareUART *uart, uint8_t data) 
{
    portENTER_CRITICAL(&uart->lock);
    uint64_t star = esp_cpu_get_cycle_count(); 
    // 发送起始位(低电平)
    gpio_set_level(uart->tx_pin, 0);
    //持续一个bit
    wait_time(&star , uart->bit_cycles);
    // 发送8位数据(LSB先发送)
    for (int i = 0; i < 8; i++) 
    {
        gpio_set_level(uart->tx_pin, (data >> i) & 0x01);
        //持续一个bit
        wait_time(&star , uart->bit_cycles);
    }

    // 发送停止位(高电平)
    gpio_set_level(uart->tx_pin, 1);
    //持续一个bit
    wait_time(&star , uart->bit_cycles);
    portEXIT_CRITICAL(&uart->lock);
}

想要更详尽的代码,请点击文章开头连接。感谢各位阅读。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值