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?
Если кому идея понравилась, ниже найдете питоновый код — пользуйтесь на доброе здоровье, он преобразует любые файлы в картинки и обратно — картинки в файлы. Главное — порядок картинок не перепутать, это никак не контролируется — хотя и можно, но лень.
#!/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 все это работать не будет. Некоторое время пришлось убить, чтобы все это перелопатить — кроме меня, все это никто не тестировал, но у меня пока все работало без проблем — мой тестовый пример, где я тестировал таймер и последовательный интерфейс.
#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 с сантиметровой точность и дорогая, и под деревьями работать не будет. Пылесосные лидары солнечный свет плохо переносят — косить по ночам? Ну, да — «в самый жуткий час мы волшебную косим трын-траву!». Думал про фазовую радиопеленгацию — за небольшие деньги точность получается метр и хуже. С оптикой свои проблемы — но, наверно, самые простые. Короче, буду дальше думать и косить бензиновой жужжалкой. А то заедет на соседский газон — проблем не оберешься. Сосед — какой-то косильный маньяк, и если его лишить удовольствия косить свой газон — я даже боюсь думать о последствиях.
На сем позвольте откланяться, надеюсь кому-то пригодится.
+54 |
3145
58
|
+27 |
2375
63
|
а для широкого круга обывателй, можно рассказать, как из этого градусник «запилить»?
А, то не совсем понятно зачем все это нужно.
(Лайкнул, конечно, добра автору и творческих успехов, от всей души желаю)
Нужно просто завести армию фанатов!
Пример:
Эмоциональная поклонница сжимает в руках пучок травы, по которому прошёл Ринго Старр — ударник группы The Beatles. Аэропорт Лос-Анджелеса, США, 1964 год.
Ничего страшного, бывает
А читать все это Вас вроде никто и не заставлял.;)
А с процессорами у меня очень хорошо — еще бы где силы найти все MSP430 выбросить. В свое время их можно было бесплатными образцами набрать — но не более 50 штук и 6 разновидностей за сутки :). У меня из столько, конечно, нет — но место все равно занимают — каждой твари по паре — и выбросить жалко.
Но времена халявы кончились — а много лет назад за домашние проекты платить вообще не приходилось, сплошные образцы.
Opencv?
Задача решается гораздо проще — без всяких танцев с бубном вокруг питона:
или На выходе получается контейнер, который читается простым WinRAR.
Что касается загрузчика — с PY32F040 он будет работать?