Авторизация
Регистрация

Напомнить пароль

PY32F002A — Cortex-M0+ 32KB flash, 4KB RAM: ардуиним ниже плинтуса

Если кто-то из проницательных читателей сталкивался с этим очередным китайским чудом, наверняка спросит — какую траву автор курит? У них же по спецификации 20 KB флеша и 3 KB оперативки. Странная цифра 3 в наше время, когда у каждого настоящего программиста 16 пальцев на руках — чтобы считать было легче. Но еще не вечер, подождите и почитайте дальше. Мимо такого чуда, даже если спецификации были бы правдой, пройти невозможно — его продают за 9...11 центов штука в розницу. Увидев такую халяву, я немедленно заказал 20 штук на попробовать. Попробовав — еще с полсотни, в кулацком хозяйстве пулемет завсегда сгодится. За такую цену эта вещь для пионеров и пенсионеров — действительно вещь.

Для интересующихся — все равно же кто-то спросит — я его покупал здесь.

Любителем Windows больших проблем с программированием этих контроллеров нет, Keil uVision поддерживает эти микроконтроллеры, хотя и не их «каропки», но все же. А с сайта Puya Semiconductor можно скачать загрузчик, позволяющий с помощью последовательного интерфейса прошивать эти контроллеры. Но Keil и разработка… Нет, все понятно — их компиляторы лучшие. Но редактор — Боже мой, как можно так жить? Лет 30 назад лучше ничего и не было, но сейчас же куча редакторов, которые и дополнять код умеют, и 90 процентов ошибок еще на этапе написания выявят и помогут их исправить? И все это совершенно безвозмездно, т. е. даром.

У Keil есть еще один неоспоримый плюс — его можно поставить под Wine в Linux вообще без проблем. Естественно, вся отладочная аппаратура работать не будет, но компилировать можно. Учитывая, что программы размером до 32 кбайт он компилирует без денег — для PY32F002A и любительских проектов очень подходит.

С линуксистами несколько сложнее, но с конце концов, оказывается даже лучше.

 

— Что же из этого следует? — Следует жить,

шить сарафаны и легкие платья из ситца.

— Вы полагаете, все это будет носиться?

— Я полагаю, что все это следует шить.

 

Когда эти микросхемы пришли, я два дня убил, пытаясь заставить их работать через SWD и свисток-клон st-link. В конце-концов терпение лопнуло и хотел было все это забросить. Но если китайцам хватает последовательного загрузчика, почему мне это должно быть в падлу? Вроде религия позволяет, и миллионы ардуинщиков вполне довольны загрузкой AVR и всяких ESP32 через адаптеры последовательного порта USB? Миллионы мух не могут ошибаться.

 

Еще за два дня я напитонил такой загрузчик. До ума не довел — но он читает память, записывает, сбрасывает процессор — да вот возьмите его здесь и пользуйтесь на доброе здоровье, хоть под Linux, хоть под Windows. Может, потом сделаю автоматический перевод PY32F002A в режим загрузки и сброс и допишу свой загрузчик — а может кнопки буду жать.

Загрузчик лежит здесь — но скачать может быть проблемой, mail.ru стал давать скачивать файл только 5 раз в день. Меркантильные кю, куда этот мир катится? За снятие ограничений рубль просят — а где я вам его возьму?

Пока я этот загрузчик писал, решил заодно тест памяти сделать — ну не может микросхема такого сорта за такую цену быть без подвоха. Когда я тестировал флеш, чип смотрел на меня, как кот, который знает, где спрятал колбасу: подвох нашелся — 20 кбайт протестировалась, а он все тестирует и тестирует. Натестировал 32 кбайта, на этом остановился. Интересно девки пляшут…

А сколько же оперативки тогда? С нормальным тестом обломился — оказалось, что встроенный загрузчик позволяет из оперативной памяти читать только по 4 байта, а писать вообще не позволяет. Ну да ладно, читаю каждый 256-байт и смотрю на его случайность — как должно быть после сброса. После примитивного теста — 4 кбайта у микропроцессора всяко разно есть.

Стал искать, что поэтому поводу в интернетах пишут — да, не я первый. Народ уже давно нашел у чипа и избыток памяти, и обнаружил недокументированный PLL, с которым контроллер уже и на 48 МГц работать может. Особо буйные разогнали его на 64 МГц, но, говорят, уже не вся периферия держится, ну и скорость чтения флеша уже подстраивать надо. Так же у него обнаружили еще один UART, SPI и якобы несуществующий DMA. Судя по всем найденным ништякам — это PY32F030.

Крвткий список обещаний и реальности:
 

Cortex-M0+ (24 МГц) -> Cortex-M0+ (48 МГц) с PLL и DMA
Flash 20 КБ -> 32 КБ
RAM 3 КБ -> 4 КБ
UART 1 -> 2
SPI 1 -> 2
ADC 1x12-bit
Компаратор - 2
I²C
TIM1, TIM16, LPTIM  -> +3 таймера
Питание 1.7–5.5 В

Небольшое лирическое отступление — так как PlusPda не позволяет никаких вложений, кроме графики, и размер текста ограничен — а исходники программатора перевалили 30 килобайт, стал думать, а как же все это обойти? Выход оказался прост — заархивировал исходники и разбил их в несколько QR кодов. Самый большой их них позволяет хранить почти 3 килобайта, заархивированные исходники заняли 4 картинки. Интересно, какой длинны нужен забор, чтобы на него наклеить закартиненный инсталлятор Windows?
Если кому идея понравилась, ниже найдете питоновый код — пользуйтесь на доброе здоровье, он преобразует любые файлы в картинки и обратно — картинки в файлы. Главное — порядок картинок не перепутать, это никак не контролируется — хотя и можно, но лень.

 

qr_file.py
#!/usr/bin/env python3
import argparse
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import SquareModuleDrawer
import os
import sys
import zlib
import base64
import re
import warnings
from typing import List
from PIL import Image
import pyzbar.pyzbar as pyzbar

# Suppress warnings
warnings.filterwarnings("ignore")
os.environ['ZBAR_SILENT'] = '1'

def show_usage_instructions():
    print("""
QR File Encoder/Decoder Tool
===========================

Usage:
  To encode a file into QR code(s):
    qr_file.py encode -i <input_file> [-o <output_template>] [-v <version>] [-e <error_level>] [-b <box_size>]
  
  To decode QR code(s) back to a file:
    qr_file.py decode -i <input_qr> -o <output_file>

Options:
  encode:
    -i, --input       Input file to encode (required)
    -o, --output      Output template (default: output.png)
    -v, --version     Max QR version (1-40, optional)
    -e, --error       Error correction level (L,M,Q,H, default: L)
    -b, --box         QR module size in pixels (default: 10)

  decode:
    -i, --input       Input QR image (required)
    -o, --output      Output file name (required)

Examples:
  qr_file.py encode -i document.pdf -o doc_qr.png
  qr_file.py decode -i doc_qr.png -o restored.pdf

Note: For large files, multiple QR codes will be created automatically.
""")

def calculate_max_qr_size(version: int, error_correction: int) -> int:
    """Calculate maximum data size for specified version and error correction level"""
    capacity_table = {
        qrcode.constants.ERROR_CORRECT_L: [
            17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321, 367, 425, 458, 520, 586, 644, 718, 792, 858,
            929, 1003, 1091, 1171, 1273, 1367, 1465, 1528, 1628, 1732, 1840, 1952, 2068, 2188, 2303, 2431,
            2563, 2699, 2809, 2953
        ],
        qrcode.constants.ERROR_CORRECT_M: [
            14, 26, 42, 62, 84, 106, 122, 152, 180, 213, 251, 287, 331, 362, 412, 450, 504, 560, 624, 666,
            711, 779, 857, 911, 997, 1059, 1125, 1190, 1264, 1370, 1452, 1538, 1628, 1722, 1809, 1911, 1989,
            2099, 2213, 2331
        ],
        qrcode.constants.ERROR_CORRECT_Q: [
            11, 20, 32, 46, 60, 74, 86, 108, 130, 151, 177, 203, 241, 258, 292, 322, 364, 394, 442, 482,
            509, 565, 611, 661, 715, 751, 805, 868, 908, 982, 1030, 1112, 1168, 1228, 1283, 1351, 1423, 1499,
            1579, 1663
        ],
        qrcode.constants.ERROR_CORRECT_H: [
            7, 14, 24, 34, 44, 58, 64, 84, 98, 119, 137, 155, 177, 194, 220, 250, 280, 310, 338, 382,
            403, 439, 461, 511, 535, 593, 625, 658, 698, 742, 790, 842, 898, 958, 983, 1051, 1093, 1139, 1219, 1273
        ]
    }
    return capacity_table[error_correction][version-1] if version <= 40 else capacity_table[error_correction][-1]

def find_optimal_version(data_size: int, error_correction: int) -> int:
    """Find minimum QR version that can accommodate the data"""
    for version in range(1, 41):
        if data_size <= calculate_max_qr_size(version, error_correction):
            return version
    raise ValueError("Data too large for QR code (max version 40)")

def split_filename(filename: str) -> tuple:
    """Split filename into base name and number"""
    match = re.match(r'^(.*?)(\d+)?$', os.path.splitext(filename)[0])
    return match.group(1), int(match.group(2)) if match.group(2) else None

def encode_file(input_file: str, output_template: str, qr_version: int = None, 
               error_correction: int = qrcode.constants.ERROR_CORRECT_L, box_size: int = 10):
    """Encode file into one or multiple QR codes"""
    with open(input_file, 'rb') as f:
        file_data = f.read()
    
    compressed_data = zlib.compress(file_data)
    encoded_data = base64.b64encode(compressed_data)
    
    max_chunk_size = calculate_max_qr_size(40, error_correction) - 10
    chunks = [encoded_data[i:i+max_chunk_size] for i in range(0, len(encoded_data), max_chunk_size)]
    
    if len(chunks) > 1:
        print(f"File too large, splitting into {len(chunks)} QR codes")
    
    for i, chunk in enumerate(chunks):
        chunk_with_meta = f"{i+1}/{len(chunks)}:".encode() + chunk
        optimal_version = find_optimal_version(len(chunk_with_meta), error_correction)
        if qr_version is not None:
            optimal_version = min(optimal_version, qr_version)
        
        qr = qrcode.QRCode(
            version=optimal_version,
            error_correction=error_correction,
            box_size=box_size,
        )
        
        qr.add_data(chunk_with_meta)
        qr.make(fit=True)
        
        img = qr.make_image(image_factory=StyledPilImage, module_drawer=SquareModuleDrawer())
        
        if len(chunks) > 1:
            base, ext = os.path.splitext(output_template)
            output_file = f"{base}{i+1:02d}{ext}"
        else:
            output_file = output_template
        
        img.save(output_file)
        print(f"Created QR code: {output_file} (version {optimal_version})")

def find_related_qr_files(base_file: str) -> List[str]:
    """Find all related QR files for assembly"""
    base, ext = os.path.splitext(base_file)
    base_name, _ = split_filename(os.path.basename(base))
    
    dir_path = os.path.dirname(base_file) or '.'
    pattern = re.compile(rf'^{re.escape(base_name)}(\d+){re.escape(ext)}$')
    
    related_files = []
    for f in os.listdir(dir_path):
        if pattern.match(f):
            related_files.append(os.path.join(dir_path, f))
    
    if not related_files:
        return [base_file]
    
    related_files.sort(key=lambda x: int(split_filename(os.path.basename(x))[1]))
    return related_files

def decode_file(input_file: str, output_file: str):
    """Decode one or multiple QR codes back to file"""
    qr_files = find_related_qr_files(input_file)
    print(f"Found {len(qr_files)} QR files to process")
    
    all_data = bytearray()
    total_parts = None
    
    for qr_file in qr_files:
        try:
            img = Image.open(qr_file)
            decoded = pyzbar.decode(img)
            
            if not decoded:
                raise ValueError(f"Failed to decode QR code: {qr_file}")
            
            data = decoded[0].data
            
            if b':' in data:
                meta, chunk = data.split(b':', 1)
                part_info = meta.decode().split('/')
                current_part = int(part_info[0])
                total_parts = int(part_info[1]) if total_parts is None else total_parts
                
                if total_parts != int(part_info[1]):
                    raise ValueError("Mismatch in QR code parts count")
            else:
                chunk = data
                if len(qr_files) > 1:
                    raise ValueError("Missing part number information in QR code")
            
            all_data.extend(chunk)
            print(f"Processed QR code: {qr_file}")
            
        except Exception as e:
            raise ValueError(f"Error processing {qr_file}: {str(e)}")
    
    if total_parts is not None and len(qr_files) != total_parts:
        raise ValueError(f"Found {len(qr_files)} parts, expected {total_parts}")
    
    try:
        compressed_data = base64.b64decode(all_data)
        file_data = zlib.decompress(compressed_data)
        
        with open(output_file, 'wb') as f:
            f.write(file_data)
        
        print(f"\nFile successfully restored: {output_file}")
        print(f"File size: {len(file_data)} bytes")
    
    except Exception as e:
        raise ValueError(f"Error decoding data: {str(e)}")

def main():
    # Show instructions if no arguments provided
    if len(sys.argv) == 1:
        show_usage_instructions()
        sys.exit(0)
        
    parser = argparse.ArgumentParser(description='Convert file to QR code(s) and back', add_help=False)
    subparsers = parser.add_subparsers(dest='command', required=True)
    
    encode_parser = subparsers.add_parser('encode', help='Convert file to QR code(s)')
    encode_parser.add_argument('-i', '--input', required=True, help='Input file')
    encode_parser.add_argument('-o', '--output', default='output.png', 
                             help='Output image template')
    encode_parser.add_argument('-v', '--version', type=int, 
                             help='Max QR code version (1-40)')
    encode_parser.add_argument('-e', '--error', default='L', choices=['L', 'M', 'Q', 'H'], 
                             help='Error correction level (L, M, Q, H)')
    encode_parser.add_argument('-b', '--box', type=int, default=10, help='QR module size')
    
    decode_parser = subparsers.add_parser('decode', help='Convert QR code(s) back to file')
    decode_parser.add_argument('-i', '--input', required=True, 
                             help='Input QR image file')
    decode_parser.add_argument('-o', '--output', required=True, help='Output file name')
    
    # Add help option manually
    parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
                      help='Show this help message and exit')
    
    args = parser.parse_args()
    
    error_mapping = {
        'L': qrcode.constants.ERROR_CORRECT_L,
        'M': qrcode.constants.ERROR_CORRECT_M,
        'Q': qrcode.constants.ERROR_CORRECT_Q,
        'H': qrcode.constants.ERROR_CORRECT_H
    }
    
    try:
        if args.command == 'encode':
            encode_file(
                args.input,
                args.output,
                qr_version=args.version,
                error_correction=error_mapping[args.error],
                box_size=args.box
            )
        elif args.command == 'decode':
            decode_file(args.input, args.output)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    main()

 

 

 

Чем то это напоминает перфокартное время — когда и перфокарты были картоннее, и трава зеленее, и деревья больше. Про девок вообще молчу.

 

Загрузка загрузкой, но ведь нужно что-то написать, чтобы это загружать можно было. Ага, что бы продать что-нибудь ненужное, нужно сперва купить что-нибудь ненужное, а у нас денег нет.

Не буду вас долго утомлять, большинству это будет абсолютно фиолетово, а для интересующихся все лежит тут.

Работает все двояко — с одной стороны в Visual Studio Code и Platformio вы можете пользоваться всеми супер-пупер возможностями хорошей среды разработки, а когда все написано — запускаете make и имеете бинарный файл, который тут же прошиваете.

 

И не благодарите — все файлы и библиотеки я стащил у Keil, естественно, с gcc все это работать не будет. Некоторое время пришлось убить, чтобы все это перелопатить — кроме меня, все это никто не тестировал, но у меня пока все работало без проблем — мой тестовый пример, где я тестировал таймер и последовательный интерфейс.

main.c
#include "py32f002ax5.h"



void Delay(uint32_t count) 
{
  while (count--)  __NOP();    
}



// Константы для конфигурации тактовой частоты
#define HSI_FREQUENCY  24000000UL // Частота HSI в Гц
#define HSI_FS_VALUE   4          // Значение для 24 МГц
// Функция настройки системного тактирования
void SystemClock_Config(void)
{
  // 1. Настройка и включение HSI (24 МГц)
  RCC->ICSCR &= ~RCC_ICSCR_HSI_FS_Msk;                // Очистка поля HSI_FS
  RCC->ICSCR |= (HSI_FS_VALUE << RCC_ICSCR_HSI_FS_Pos); // Установка 24 МГц
  RCC->CR |= RCC_CR_HSION;                            // Включение HSI
  // 2. Ожидание стабилизации HSI
  while (!(RCC->CR & RCC_CR_HSIRDY));                 // Ждем готовности HSI
  // 3. Настройка делителей и источника тактирования
  RCC->CR &= ~RCC_CR_HSIDIV_Msk;                      // Отключение делителя HSI
  RCC->CFGR &= ~(RCC_CFGR_HPRE | RCC_CFGR_PPRE);      // AHB и APB без деления
  RCC->CFGR &= ~RCC_CFGR_SW;                          // Выбор HSI как источника
  RCC->CFGR |= RCC_CFGR_MCOSEL_0;                     // Настройка MCO (HSI)
  // 4. Ожидание переключения на HSI
  while (RCC->CFGR & RCC_CFGR_SWS);                   // Ждем подтверждения
  // 5. Обновление переменной системной частоты
  SystemCoreClock = HSI_FREQUENCY;
}


void TIM1_PWM_Init(void)
{
  // Включить тактирование TIM1 и GPIOA, GPIOB
  RCC->APBENR2 |= RCC_APBENR2_TIM1EN; // Включить тактирование TIM1
  RCC->IOPENR |= RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN; // Включить тактирование GPIOA и GPIOB

  // Настройка пинов PB3 (TIM1_CH2), PA0 (TIM1_CH3), PA1 (TIM1_CH4), PA7 (TIM1_CH1)
  GPIOB->MODER &= ~(GPIO_MODER_MODE3); // Сбросить режим для PB3
  GPIOB->MODER |= GPIO_MODER_MODE3_1; // Альтернативная функция
  GPIOA->MODER &= ~(GPIO_MODER_MODE0 | GPIO_MODER_MODE1 | GPIO_MODER_MODE7); // Сбросить режим для PA0, PA1, PA7
  GPIOA->MODER |= (GPIO_MODER_MODE0_1 | GPIO_MODER_MODE1_1 | GPIO_MODER_MODE7_1); // Альтернативная функция

  GPIOB->AFR[0] &= ~GPIO_AFRL_AFSEL3; // Сбросить AF для PB3
  GPIOB->AFR[0] |= (1 << GPIO_AFRL_AFSEL3_Pos); // AF1 для TIM1_CH2

  GPIOA->AFR[0] &= ~(GPIO_AFRL_AFSEL0 | GPIO_AFRL_AFSEL1 | GPIO_AFRL_AFSEL7); // Сбросить AF для PA0, PA1, PA7
  // AF13 для TIM1_CH3, AF13 для TIM1_CH4,  AF2 для TIM1_CH1N
  GPIOA->AFR[0] |= (13 << GPIO_AFRL_AFSEL0_Pos) | (13 << GPIO_AFRL_AFSEL1_Pos) | (2 << GPIO_AFRL_AFSEL7_Pos);  

  // Настройка TIM1 для PWM 16 кГц
  TIM1->ARR = 1499; // Период для 16 кГц при 24 МГц (24,000,000 / 16,000 - 1)
  TIM1->PSC = 0; // Предделитель = 1 (без деления)

  // Настройка PWM режима для каналов 1, 2, 3, 4
  TIM1->CCMR1 &= ~(TIM_CCMR1_OC1M | TIM_CCMR1_OC2M); // Сбросить режимы каналов 1 и 2
  TIM1->CCMR1 |= (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE); // PWM Mode 1 + предзагрузка для CH1
  TIM1->CCMR1 |= (TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2PE); // PWM Mode 1 + предзагрузка для CH2
  TIM1->CCMR2 &= ~(TIM_CCMR2_OC3M | TIM_CCMR2_OC4M); // Сбросить режимы каналов 3 и 4
  TIM1->CCMR2 |= (TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3PE); // PWM Mode 1 + предзагрузка для CH3
  TIM1->CCMR2 |= (TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4PE); // PWM Mode 1 + предзагрузка для CH4

  // Включить выходы каналов (CH0 - комплементарный)
  TIM1->CCER |= TIM_CCER_CC1NE | TIM_CCER_CC2E | TIM_CCER_CC3E | TIM_CCER_CC4E;

  // Установить duty cycle
  TIM1->CCR1 = 150; // 10% для CH1 (PA7 NE)
  TIM1->CCR2 = 375; // 25% для CH2 (PB3)
  TIM1->CCR3 = 750; // 50% для CH3 (PA0)
  TIM1->CCR4 = 1125; // 75% для CH4 (PA1)

  // Включить автопредзагрузку и таймер
  TIM1->CR1 |= TIM_CR1_ARPE; // Включить автопредзагрузку
  TIM1->BDTR |= TIM_BDTR_MOE; // Включить основные выходы
  TIM1->CR1 |= TIM_CR1_CEN; // Включить TIM1
}


// Инициализация UART (PA2 - TX, PA3 - RX)
void UART_Init(void)
{
  // Включить тактирование USART1
  RCC->APBENR2 |= RCC_APBENR2_USART1EN;
  
  // Настройка пинов PA2 (TX) и PA3 (RX)
  GPIOA->MODER &= ~(GPIO_MODER_MODE2 | GPIO_MODER_MODE3); // Сбросить режим
  GPIOA->MODER |= (GPIO_MODER_MODE2_1 | GPIO_MODER_MODE3_1); // Альтернативная функция
  GPIOA->AFR[0] &= ~(GPIO_AFRL_AFSEL2 | GPIO_AFRL_AFSEL3); // Сбросить AF
  GPIOA->AFR[0] |= (1 << GPIO_AFRL_AFSEL2_Pos) | (1 << GPIO_AFRL_AFSEL3_Pos); // AF1 для USART1

  // Настройка USART1: 115200 бод, 8 бит, 1 стоп-бит, без паритета
  //USART1->BRR = SystemCoreClock / 115200; // Делитель для 115200 бод при 24 МГц
  // HSI not calibrated well
  USART1->BRR = 22300000 / 115200; // Делитель для 115200 бод при 24 МГц  
  USART1->CR1 = USART_CR1_RE | USART_CR1_TE | USART_CR1_UE | USART_CR1_RXNEIE; // Включить приемник, передатчик, USART и прерывание по приему
  NVIC_EnableIRQ(USART1_IRQn); // Включить прерывание USART1
}

// Функция отправки символа по UART
void UART_Transmit(uint8_t data)
{
  while (!(USART1->SR & USART_SR_TXE)); // Ждать, пока буфер передачи не пуст
  USART1->DR = data; // Отправить символ
}

// Переменная для хранения последнего принятого печатаемого символа
volatile uint8_t last_printable_char = '#'; // По умолчанию '#'
volatile uint8_t rx_received = 0; // Флаг получения символа

// Обработчик прерывания USART1
void USART1_IRQHandler(void)
{
  if (USART1->SR & USART_SR_RXNE) // Проверка флага RXNE
  {
    uint8_t received = USART1->DR; // Чтение принятого символа
    if (received >= 32 && received <= 126) // Проверка на печатаемый символ
    {
      last_printable_char = received; // Сохранение символа
      rx_received = 1; // Установка флага получения
    }
  }
}


int main(void) 
{
  SystemClock_Config();

  // Настройка GPIOB
  RCC->IOPENR |= RCC_IOPENR_GPIOBEN; // Включить тактирование GPIOB
  GPIOB->MODER &= ~GPIO_MODER_MODE0; // Сбросить режим
  GPIOB->MODER |= GPIO_MODER_MODE0_0; // Выход
  GPIOB->OTYPER &= ~GPIO_OTYPER_OT0; // Push-Pull

  TIM1_PWM_Init();
  
  UART_Init();

  while (1) 
  {
    GPIOB->ODR ^= GPIO_ODR_OD0; // Переключить пин
    UART_Transmit(last_printable_char); // Отправить последний печатаемый символ или '#'
    Delay(500000); // Задержка
  }
}

Ну а с ардуино я пошутил — не собираюсь я писать ардуино фреймворк, зачем мне это? Может кто напишет, а может и уже где-то лежит написанный. А для большинства народа любая плата с микропроцессором — это ардуино, детали никому не интересны.

Нафига козе баян — вообще-то я хотел переделать газонокосилку, простую тупую газонокосилку, которая ездит как попало по газону, ограниченному проводом-периметром.

Со временем — кстати, не таким и большим, ей чуть больше 5 лет, у нее разъемы пришли в состояние, как было когда-то у советских ЭВМ — попинаешь платы, пошевелишь разъемы — она дальше работает. Подозреваю, связь самая прямая — ее произвела фирма из Израиля, а там сплошь наши люди. Вот и хотел выкинуть все провода и разъемы, оставить только питание и прокинуть LIN между модулями — CAN для нее все-таки жирновато.

А тут такой контроллер — дешевле, чем провода. И чего уж от этой газонокосилки никак не ожидал — LiFePO4 аккумуляторы за это время практически не сдулись, хотя по современным меркам их изначальная емкость не очень большая.

 

Но что делать с ее навигацией — так пока и не решил. Супер-пупер GPS с сантиметровой точность и дорогая, и под деревьями работать не будет. Пылесосные лидары солнечный свет плохо переносят — косить по ночам? Ну, да — «в самый жуткий час мы волшебную косим трын-траву!». Думал про фазовую радиопеленгацию — за небольшие деньги точность получается метр и хуже. С оптикой свои проблемы — но, наверно, самые простые. Короче, буду дальше думать и косить бензиновой жужжалкой. А то заедет на соседский газон — проблем не оберешься. Сосед — какой-то косильный маньяк, и если его лишить удовольствия косить свой газон — я даже боюсь думать о последствиях.

На сем позвольте откланяться, надеюсь кому-то пригодится.

 

Добавить в избранное
+43 +51
свернутьразвернуть
Комментарии (31)
RSS
+
avatar
+8
Обалдеть. Обзор из поговорок. Шанхайские барсы, хитрая на выдумки голь и трын-трава в одном флаконе.
+
avatar
0
а я всё думал: как же это назвать. благодарю за определение)
+
avatar
+12
  • -kan-
  • 22 июня 2025, 12:43
Ничего не понятно, но очень интересно
+
avatar
+1
Интересно,
а для широкого круга обывателй, можно рассказать, как из этого градусник «запилить»?
А, то не совсем понятно зачем все это нужно.
(Лайкнул, конечно, добра автору и творческих успехов, от всей души желаю)
+
avatar
+3
  • DVANru
  • 22 июня 2025, 12:56
Короче, буду дальше думать и косить бензиновой жужжалкой.
У селебрити таких проблем нет — фанаты косят вручную!
Нужно просто завести армию фанатов!
Пример:
Эмоциональная поклонница сжимает в руках пучок травы, по которому прошёл Ринго Старр — ударник группы The Beatles. Аэропорт Лос-Анджелеса, США, 1964 год.
+
avatar
+1
Куракоды — это, конечно, хорошо, но есть же старый добрый uuencode! :-)
+
avatar
+1
До сих пор практикую base64.
+
avatar
0
А просто залить на GitHub? Да не… слишком просто.
+
avatar
0
RAR-JPEG жи есть.
+
avatar
0
Насчет навигации — что думаете про FTM в ESP32C3?
+
avatar
0
Думал — даже при 4 антеннах с базой полметра, точности никакой не получается — на краях участка хуже метра. Кроме того эти 4 засинхронизировать надо — не реально.
+
avatar
+4
о чём тут вообще!?
+
avatar
+1
  • INN36
  • 22 июня 2025, 14:24
+
avatar
0
считаете Афтырь заслужил Шнобелевку!?
+
avatar
0
  • INN36
  • 22 июня 2025, 14:51
Просто автор захотел выговориться.
Ничего страшного, бывает
+
avatar
0
я не против, просто время жалко)
+
avatar
0
  • INN36
  • 22 июня 2025, 15:18
Не стреляйте в пианиста…
А читать все это Вас вроде никто и не заставлял.;)
+
avatar
+5
перестаньте курить то, что скосили. это явно не то, что нужно курить
+
avatar
+1
Курить вредно, так то.
+
avatar
+6
Эмм, а смысл всего этого в эпоху ESP? Он дороже пусть и в два раза (но разница 1$ или 2$, ИМХО, несущественна для diy), изучен вдоль и поперек, имеет нормальные спеки и с ним куча бордов, а программировать можно хоть из ардуино.
+
avatar
+1
  • CuMr
  • 22 июня 2025, 13:56
0,1 и 1 — это не в два раза…
+
avatar
+1
С удовольствием бы использовал вместо attiny75 но «порог вхождения» высоковат.
+
avatar
+5
  • Leoniv
  • 22 июня 2025, 13:34
Смысл мегадешевых контроллеров — это если что-то делать мегамассово и экономить каждый цент. А для DIY нет никакой разницы, или этот за 10 центов, или STM32F030 за 45 центов.
+
avatar
+1
А поиграться?
А с процессорами у меня очень хорошо — еще бы где силы найти все MSP430 выбросить. В свое время их можно было бесплатными образцами набрать — но не более 50 штук и 6 разновидностей за сутки :). У меня из столько, конечно, нет — но место все равно занимают — каждой твари по паре — и выбросить жалко.
Но времена халявы кончились — а много лет назад за домашние проекты платить вообще не приходилось, сплошные образцы.
+
avatar
0
Робот-пылесос как-то справляется, может у него мозги позаимствовать?
+
avatar
0
он не на открытом воздухе и с прямыми солнечными лучами, это большая разница.
+
avatar
0
  • macau
  • 22 июня 2025, 14:44
>Но что делать с ее навигацией — так пока и не решил
Opencv?
+
avatar
0
Вот тут ссылка один чел ещё 10 лет назад решал эти проблемы.
+
avatar
0
так как PlusPda не позволяет никаких вложений, кроме графики, и размер текста ограничен — а исходники программатора перевалили 30 килобайт, стал думать, а как же все это обойти? Выход оказался прост — заархивировал исходники и разбил их в несколько QR кодов. Самый большой их них позволяет хранить почти 3 килобайта, заархивированные исходники заняли 4 картинки.
Тот самый случай, когда думать — вредно.
Если кому идея понравилась, ниже найдете питоновый код
Идея НЕ понравилась.
Задача решается гораздо проще — без всяких танцев с бубном вокруг питона:
cat image1.jpg something.rar > image2.jpg
или
copy /b image1.jpg+something.rar image2.jpg
На выходе получается контейнер, который читается простым WinRAR.

Что касается загрузчика — с PY32F040 он будет работать?
+
avatar
0
  • AndrVU
  • 22 июня 2025, 15:18
у каждого настоящего программиста 16 пальцев на руках — чтобы считать было легче
Восемь тоже было бы неплохо. Как у котов когтей на двух лапах (про пятый неразвитый я в курсе). Интенсивность развития цифровых технологий была бы совсем иной, но, видимо, у эволюции были другие приоритеты.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.