作者共發了11篇帖子。 內容轉換:不轉換▼
 
點擊 回復
837 10
【程序】使用数码管显示lwip网页访问的次数
一派護法 十九級
1樓 發表于:2017-4-2 19:41
修改前的工程:https://zh.arslanbar.net/post.php?t=24586
【最终效果】
打开网页时,自动把数字加1,并显示数码管上显示的数字。
例如访问网页“http://enc28j60/wel”时,显示的内容如下:
请求的网页文件名称是: /wel
num=170

同时,数码管动态扫描显示数字170。
一派護法 十九級
2樓 發表于:2017-4-2 19:45
首先打开main.c文件,添加全局变量:
uint8_t num = 0;
const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};
在main函数中的lwip_init函数前,添加如下代码:
// 配置数码管扫描
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 数码管扫描端口 (74HC595驱动)
GPIOB->CRH = 0x00000033;
GPIOB->CRL = 0x30000000;
RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; // 用定时器7来定时扫描
TIM7->ARR = 49; // 扫描频率: 50*0.1ms=5ms
TIM7->PSC = 7199; // 0.1ms基准
TIM7->DIER = TIM_DIER_UIE;
NVIC_EnableIRQ(TIM7_IRQn); // 开中断
TIM7->EGR = TIM_EGR_UG; // 触发中断, 立即点亮数码管
TIM7->CR1 = TIM_CR1_CEN; // 开定时器
一派護法 十九級
3樓 發表于:2017-4-2 19:47

在main函数下添加定时器7的中断服务函数:
void TIM7_IRQHandler(void)
{
    static uint8_t i = 0;
    static uint8_t numbuf;
    TIM7->SR &= ~TIM_SR_UIF;
    if (i == 0)
        numbuf = num;
    ser_in(seg8[numbuf % 10]);
    ser_in(1 << i);
    par_out();
    numbuf /= 10;
    i = (i + 1) % 3; // 总共3个数码管
}

该函数每次只点亮一个数码管,从低位扫描到高位。

一派護法 十九級
4樓 發表于:2017-4-2 19:54

本程序的关键是解决数码管扫描中断打断lwip和网卡驱动原本的程序而卡死的问题。有两种解决方法:
1. 在main函数的主循环中开关中断:
while (1)
{
  __disable_irq();
  // lwip处理
  __enable_irq();
}
2. 把上述语句添加在网卡驱动程序中与SPI有关的函数里。主要是ENC28J60.c文件中如下4个函数:
ENC28J60_ReadBuf
ENC28J60_ReadOP
ENC28J60_WriteBuf
ENC28J60_WriteOP

显然方法2要更好一些,毕竟关中断的时间要比方法1短得多。数码管的扫描也就不容易出现闪烁。

一派護法 十九級
5樓 發表于:2017-4-2 19:57
采用方法2后,就算在浏览器里一直按着F5刷新键不放,程序也不会卡死,并且数码管丝毫没有闪烁。
一派護法 十九級
6樓 發表于:2017-4-2 19:58
【修改后的4个网卡驱动函数】
// 读缓冲区
void ENC28J60_ReadBuf(uint8_t *p, uint16_t len)
{
    uint8_t data;
    uint8_t first = 1;
    if (len == 0)
        return;
    __disable_irq();
    SPI1->CR1 |= SPI_CR1_SPE;
    SPI1->DR = ENC28J60_READ_BUF_MEM; // 通过SPI发送读取缓冲区命令
    while ((SPI1->SR & SPI_SR_TXE) == 0);
   
    // 循环读取
    while (len--)
    {
        SPI1->DR = 0xff; // 送入下一次要发送的数据
        while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待本次数据发送完毕
        data = SPI1->DR; // 接收本次数据
        if (first)
            first = 0; // 忽略第一次数据
        else
            *p++ = data;
        while ((SPI1->SR & SPI_SR_TXE) == 0);
    }
    while ((SPI1->SR & SPI_SR_RXNE) == 0);
    *p = SPI1->DR; // 接收最后一次数据
    while (SPI1->SR & SPI_SR_BSY);
    SPI1->CR1 &= ~SPI_CR1_SPE;
    __enable_irq();
}

uint8_t ENC28J60_ReadOP(uint8_t op, uint8_t addr)
{
    uint8_t data;
    __disable_irq();
    SPI1->CR1 |= SPI_CR1_SPE; // 启动SPI总线, 同时自动拉低片选信号CS
    SPI1->DR = op | (addr & 0x1f); // 操作码和地址 (1)
    while ((SPI1->SR & SPI_SR_TXE) == 0); // TXE置位时数据(1)刚好发完1位, 可以向DR中放入下次要发送的数据
    SPI1->DR = 0xff; // 送入数据 (2)
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待数据(1)发送完毕
    data = SPI1->DR; // 接收 (1)
    while ((SPI1->SR & SPI_SR_TXE) == 0); // TXE置位时数据(2)刚好发完1位
    if (addr & 0x80) // 如果是MAC和MII寄存器, 第一个读取的字节无效, 该信息包含在地址的最高位
        SPI1->DR = 0xff; // 送入数据 (3), 再次通过SPI读取数据
    while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待数据(2)发送完毕
    data = SPI1->DR; // 接收 (2)
    if (addr & 0x80)
    {
        while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待数据(3)发送完毕
        data = SPI1->DR; // 接收 (3)
    }
    while (SPI1->SR & SPI_SR_BSY);
    SPI1->CR1 &= ~SPI_CR1_SPE; // 关闭SPI总线, 同时自动拉高片选信号CS
    __enable_irq();
    return data;
}

// 写缓冲区
void ENC28J60_WriteBuf(uint8_t *p, uint16_t len)
{
    __disable_irq();
    SPI1->CR1 |= SPI_CR1_SPE;
    SPI1->DR = ENC28J60_WRITE_BUF_MEM; // 发送写取缓冲区命令
    while ((SPI1->SR & SPI_SR_TXE) == 0);
   
    // 循环发送
    while (len--)
    {
        SPI1->DR = *p++;
        while ((SPI1->SR & SPI_SR_TXE) == 0);
    }
    while (SPI1->SR & SPI_SR_BSY);
    SPI1->CR1 &= ~SPI_CR1_SPE;
    __enable_irq();
}

void ENC28J60_WriteOP(uint8_t op, uint8_t addr, uint8_t data)
{
    __disable_irq();
    SPI1->CR1 |= SPI_CR1_SPE;
    SPI1->DR = op | (addr & 0x1f); // 发送操作码和寄存器地址
    while ((SPI1->SR & SPI_SR_TXE) == 0);
    SPI1->DR = data; // 发送数据
    while ((SPI1->SR & SPI_SR_TXE) == 0);
    while (SPI1->SR & SPI_SR_BSY);
    data = SPI1->DR; // 清RxNE
    SPI1->CR1 &= ~SPI_CR1_SPE;
    __enable_irq();
}
一派護法 十九級
7樓 發表于:2017-4-2 20:01
现在需要把数码管上的数字显示到网页上。打开httptest.c文件,按提示修改代码:
#include <string.h>
#include "lwip/tcp.h" // 一般情况下需要包含的头文件

#define STR_AND_SIZE(str) (str), strlen(str)

extern uint8_t num; // ★导入main.c文件中的全局变量

err_t http_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    // ............
    if (p != NULL)
    {
        // 提取页面名称
        // .................
       
        // 生成HTML内容
        num++; // ★网页上显示的数字应该和数码管上的一样
        sprintf(str, "<meta charset=\"gb2312\"><title>获取网页名称</title><div style=\"font-family:Arial\"><b>请求的网页文件名称是: </b>%s<br><b>num=</b>%d</div>", name, num); // ★修改网页内容

..............................
一派護法 十九級
8樓 發表于:2017-4-2 20:05
不过,如果收发大量的数据包,ENC28J60_WriteBuf或ENC28J60_ReadBuf的执行时间过长,那么中断就会关闭很长的时间,这样数码管就有可能闪烁。为了改善这种情况,可以考虑把SPI的操作都改成中断的形式,并且该中断的抢占优先级要比数码管的高,并注意不影响74HC595的时序,这样效果就要好得多。
一派護法 十九級
9樓 發表于:2017-4-2 20:07
最后为了保险起见,最好在主程序中使用独立看门狗IWDG,并及时喂狗。万一程序由于某种原因死机了,可以自动恢复过来而不至于服务器宕机导致网页打不开。
可以把自动分配的IP地址和数码管上显示的数字放到BKP备用寄存器里面,复位之后不会丢失。
一派護法 十九級
10樓 發表于:2017-4-12 19:30
【不关中断时程序卡死在SPI的while循环里的原因】
程序流程如下(连续发送模式):
送数据1;
等DR变为空(TXE=0);
送数据2;
等数据1发送完毕(RXNE=0);   (1)
接收数据1;   (2)
等数据2发送完毕(RXNE=0);   (3)
接收数据2;

如果数据2已经发送完毕了数据1都还没读取,那么数据1将会丢失。
由于没有关闭中断,那么在等待数据1发送完毕(步骤1)时进了数码管扫描中断,那么等到中断执行完毕后,数据2早就发送完毕了,因此步骤2实际接收到的是数据2而非数据1,数据1已经丢失。并且由于一直没有新的数据到来,RXNE始终不会被置1,因此程序就卡死在了(3)里面。
一派護法 十九級
11樓 發表于:2017-4-12 19:34

解决办法除了关闭中断以外,还可以把程序流程改成非连续发送模式:
送数据1;
等待数据1发送完毕; // while循环
接收数据1;
送数据2;
等待数据2发送完毕; // while循环
接收数据2;

这样就可以保证一定不会出问题。


如果一定要使用连续发送模式,那么就应该使用SPI接收中断,其抢占优先级应该比数码管扫描中断更高,这样就能确保不会丢失数据,程序也不会卡死在while循环里。

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
(快捷鍵:Ctrl+Enter)
 

本帖信息

點擊數:837 回複數:10
評論數: ?
作者:巨大八爪鱼
最後回復:巨大八爪鱼
最後回復時間:2017-4-12 19:34
 
©2010-2024 Arslanbar Ver2.0
除非另有聲明,本站採用創用CC姓名標示-相同方式分享 3.0 Unported許可協議進行許可。